@hanzo/rooms 0.1.0

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.
Files changed (235) hide show
  1. package/LICENSE-APACHE +205 -0
  2. package/LICENSE-MIT +21 -0
  3. package/README.md +23 -0
  4. package/dist/Account.d.ts +8 -0
  5. package/dist/Account.js +58 -0
  6. package/dist/Beluga.d.ts +12 -0
  7. package/dist/Beluga.js +152 -0
  8. package/dist/Billing.d.ts +1 -0
  9. package/dist/Billing.js +31 -0
  10. package/dist/Board.d.ts +9 -0
  11. package/dist/Board.js +447 -0
  12. package/dist/Boxes.d.ts +1 -0
  13. package/dist/Boxes.js +173 -0
  14. package/dist/CalScheduler.d.ts +15 -0
  15. package/dist/CalScheduler.js +196 -0
  16. package/dist/Chat.d.ts +35 -0
  17. package/dist/Chat.js +1049 -0
  18. package/dist/Column.d.ts +15 -0
  19. package/dist/Column.js +88 -0
  20. package/dist/Contacts.d.ts +1 -0
  21. package/dist/Contacts.js +301 -0
  22. package/dist/Directory.d.ts +24 -0
  23. package/dist/Directory.js +356 -0
  24. package/dist/Drive.d.ts +2 -0
  25. package/dist/Drive.js +466 -0
  26. package/dist/Find.d.ts +7 -0
  27. package/dist/Find.js +82 -0
  28. package/dist/Guide.d.ts +6 -0
  29. package/dist/Guide.js +272 -0
  30. package/dist/Home.d.ts +39 -0
  31. package/dist/Home.js +306 -0
  32. package/dist/Inbox.d.ts +9 -0
  33. package/dist/Inbox.js +193 -0
  34. package/dist/Market.d.ts +3 -0
  35. package/dist/Market.js +149 -0
  36. package/dist/Me.d.ts +27 -0
  37. package/dist/Me.js +251 -0
  38. package/dist/Meet.d.ts +9 -0
  39. package/dist/Meet.js +370 -0
  40. package/dist/ModeMark.d.ts +8 -0
  41. package/dist/ModeMark.js +18 -0
  42. package/dist/OrgSwitcher.d.ts +31 -0
  43. package/dist/OrgSwitcher.js +120 -0
  44. package/dist/Orgs.d.ts +10 -0
  45. package/dist/Orgs.js +582 -0
  46. package/dist/Profile.d.ts +4 -0
  47. package/dist/Profile.js +67 -0
  48. package/dist/Prose.d.ts +5 -0
  49. package/dist/Prose.js +111 -0
  50. package/dist/ProviderMark.d.ts +17 -0
  51. package/dist/ProviderMark.js +277 -0
  52. package/dist/Recents.d.ts +10 -0
  53. package/dist/Recents.js +91 -0
  54. package/dist/RightPane.d.ts +13 -0
  55. package/dist/RightPane.js +426 -0
  56. package/dist/Room.d.ts +8 -0
  57. package/dist/Room.js +50 -0
  58. package/dist/Scene.d.ts +12 -0
  59. package/dist/Scene.js +105 -0
  60. package/dist/Settings.d.ts +46 -0
  61. package/dist/Settings.js +321 -0
  62. package/dist/Setup.d.ts +1 -0
  63. package/dist/Setup.js +36 -0
  64. package/dist/Share.d.ts +3 -0
  65. package/dist/Share.js +130 -0
  66. package/dist/Shared.d.ts +1 -0
  67. package/dist/Shared.js +113 -0
  68. package/dist/Shell.d.ts +63 -0
  69. package/dist/Shell.js +1225 -0
  70. package/dist/Start.d.ts +1 -0
  71. package/dist/Start.js +96 -0
  72. package/dist/Support.d.ts +14 -0
  73. package/dist/Support.js +122 -0
  74. package/dist/Talk.d.ts +4 -0
  75. package/dist/Talk.js +107 -0
  76. package/dist/Upgrade.d.ts +11 -0
  77. package/dist/Upgrade.js +75 -0
  78. package/dist/Usage.d.ts +1 -0
  79. package/dist/Usage.js +21 -0
  80. package/dist/Vibe.d.ts +2 -0
  81. package/dist/Vibe.js +188 -0
  82. package/dist/Work.d.ts +12 -0
  83. package/dist/Work.js +256 -0
  84. package/dist/bots/Bots.d.ts +2 -0
  85. package/dist/bots/Bots.js +456 -0
  86. package/dist/bots/Lights.d.ts +8 -0
  87. package/dist/bots/Lights.js +13 -0
  88. package/dist/bots/api.d.ts +120 -0
  89. package/dist/bots/api.js +75 -0
  90. package/dist/bots/face.d.ts +5 -0
  91. package/dist/bots/face.js +13 -0
  92. package/dist/bots/ink.d.ts +7 -0
  93. package/dist/bots/ink.js +29 -0
  94. package/dist/bots/kit.d.ts +113 -0
  95. package/dist/bots/kit.js +43 -0
  96. package/dist/bots/runtime.d.ts +4 -0
  97. package/dist/bots/runtime.js +326 -0
  98. package/dist/card.d.ts +52 -0
  99. package/dist/card.js +52 -0
  100. package/dist/cast.d.ts +143 -0
  101. package/dist/cast.js +251 -0
  102. package/dist/connect.d.ts +29 -0
  103. package/dist/connect.js +64 -0
  104. package/dist/contacts-store.d.ts +111 -0
  105. package/dist/contacts-store.js +276 -0
  106. package/dist/conversations.d.ts +54 -0
  107. package/dist/conversations.js +98 -0
  108. package/dist/copy.d.ts +23 -0
  109. package/dist/copy.js +39 -0
  110. package/dist/crew.d.ts +36 -0
  111. package/dist/crew.js +304 -0
  112. package/dist/dev.d.ts +3 -0
  113. package/dist/dev.js +102 -0
  114. package/dist/enso.d.ts +70 -0
  115. package/dist/enso.js +112 -0
  116. package/dist/failure.d.ts +57 -0
  117. package/dist/failure.js +143 -0
  118. package/dist/ground.d.ts +32 -0
  119. package/dist/ground.js +32 -0
  120. package/dist/host.d.ts +76 -0
  121. package/dist/host.js +76 -0
  122. package/dist/index.d.ts +14 -0
  123. package/dist/index.js +12 -0
  124. package/dist/kept.d.ts +5 -0
  125. package/dist/kept.js +27 -0
  126. package/dist/lib/account.d.ts +49 -0
  127. package/dist/lib/account.js +229 -0
  128. package/dist/lib/ai.d.ts +46 -0
  129. package/dist/lib/ai.js +140 -0
  130. package/dist/lib/api.d.ts +43 -0
  131. package/dist/lib/api.js +62 -0
  132. package/dist/lib/bare.d.ts +9 -0
  133. package/dist/lib/bare.js +7 -0
  134. package/dist/lib/coding.d.ts +105 -0
  135. package/dist/lib/coding.js +87 -0
  136. package/dist/lib/destination.d.ts +102 -0
  137. package/dist/lib/destination.js +260 -0
  138. package/dist/lib/durable.d.ts +66 -0
  139. package/dist/lib/durable.js +107 -0
  140. package/dist/lib/first.d.ts +11 -0
  141. package/dist/lib/first.js +23 -0
  142. package/dist/lib/home.d.ts +61 -0
  143. package/dist/lib/home.js +54 -0
  144. package/dist/lib/host.d.ts +104 -0
  145. package/dist/lib/host.js +132 -0
  146. package/dist/lib/hostname.d.ts +1 -0
  147. package/dist/lib/hostname.js +14 -0
  148. package/dist/lib/hydrated.d.ts +1 -0
  149. package/dist/lib/hydrated.js +20 -0
  150. package/dist/lib/integrations.d.ts +21 -0
  151. package/dist/lib/integrations.js +1 -0
  152. package/dist/lib/intent.d.ts +6 -0
  153. package/dist/lib/intent.js +71 -0
  154. package/dist/lib/invite.d.ts +155 -0
  155. package/dist/lib/invite.js +245 -0
  156. package/dist/lib/limits.d.ts +55 -0
  157. package/dist/lib/limits.js +119 -0
  158. package/dist/lib/list.d.ts +9 -0
  159. package/dist/lib/list.js +11 -0
  160. package/dist/lib/mark.d.ts +13 -0
  161. package/dist/lib/mark.js +85 -0
  162. package/dist/lib/mix.d.ts +22 -0
  163. package/dist/lib/mix.js +22 -0
  164. package/dist/lib/models.d.ts +27 -0
  165. package/dist/lib/models.js +106 -0
  166. package/dist/lib/org.d.ts +109 -0
  167. package/dist/lib/org.js +140 -0
  168. package/dist/lib/path.d.ts +9 -0
  169. package/dist/lib/path.js +29 -0
  170. package/dist/lib/plans.d.ts +193 -0
  171. package/dist/lib/plans.js +310 -0
  172. package/dist/lib/prose.d.ts +13 -0
  173. package/dist/lib/prose.js +62 -0
  174. package/dist/lib/reach.d.ts +17 -0
  175. package/dist/lib/reach.js +70 -0
  176. package/dist/lib/rooms.json +23 -0
  177. package/dist/lib/served.d.ts +20 -0
  178. package/dist/lib/served.js +92 -0
  179. package/dist/lib/session.d.ts +211 -0
  180. package/dist/lib/session.js +493 -0
  181. package/dist/lib/share.d.ts +131 -0
  182. package/dist/lib/share.js +216 -0
  183. package/dist/lib/signout.d.ts +14 -0
  184. package/dist/lib/signout.js +40 -0
  185. package/dist/lib/tags.d.ts +27 -0
  186. package/dist/lib/tags.js +46 -0
  187. package/dist/lib/tap.d.ts +17 -0
  188. package/dist/lib/tap.js +9 -0
  189. package/dist/lib/tier.d.ts +115 -0
  190. package/dist/lib/tier.js +207 -0
  191. package/dist/lib/tiers.d.ts +9 -0
  192. package/dist/lib/tiers.js +141 -0
  193. package/dist/lib/todo.d.ts +125 -0
  194. package/dist/lib/todo.js +379 -0
  195. package/dist/look.d.ts +1 -0
  196. package/dist/look.js +21 -0
  197. package/dist/meters.d.ts +8 -0
  198. package/dist/meters.js +31 -0
  199. package/dist/model.d.ts +4 -0
  200. package/dist/model.js +44 -0
  201. package/dist/network.d.ts +64 -0
  202. package/dist/network.js +216 -0
  203. package/dist/open.d.ts +81 -0
  204. package/dist/open.js +149 -0
  205. package/dist/pane.d.ts +92 -0
  206. package/dist/pane.js +231 -0
  207. package/dist/progress.d.ts +46 -0
  208. package/dist/progress.js +51 -0
  209. package/dist/recent.d.ts +67 -0
  210. package/dist/recent.js +161 -0
  211. package/dist/rooms.d.ts +131 -0
  212. package/dist/rooms.js +195 -0
  213. package/dist/roster.d.ts +28 -0
  214. package/dist/roster.js +70 -0
  215. package/dist/span.d.ts +14 -0
  216. package/dist/span.js +50 -0
  217. package/dist/speech.d.ts +44 -0
  218. package/dist/speech.js +229 -0
  219. package/dist/stars.d.ts +16 -0
  220. package/dist/stars.js +75 -0
  221. package/dist/tabs.d.ts +26 -0
  222. package/dist/tabs.js +25 -0
  223. package/dist/team.d.ts +11 -0
  224. package/dist/team.gen.d.ts +10 -0
  225. package/dist/team.gen.js +11 -0
  226. package/dist/team.js +30 -0
  227. package/dist/tools.d.ts +35 -0
  228. package/dist/tools.js +143 -0
  229. package/dist/tours.d.ts +24 -0
  230. package/dist/tours.js +311 -0
  231. package/dist/waiting.d.ts +15 -0
  232. package/dist/waiting.js +17 -0
  233. package/dist/where.d.ts +54 -0
  234. package/dist/where.js +14 -0
  235. package/package.json +108 -0
@@ -0,0 +1 @@
1
+ export declare function Start(): import("react").JSX.Element;
package/dist/Start.js ADDED
@@ -0,0 +1,96 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ // Starting a run from here — the composer /dev's empty state promised and did
4
+ // not have.
5
+ //
6
+ // The pane already said "Describe it. Ship it." over "in your terminal, your
7
+ // editor, or right here", and `right here` was the one of the three with nothing
8
+ // behind it: the idle state drew install cards and no way to send. The comment
9
+ // that stood in for it said "there is no create-a-run route to offer: a run
10
+ // begins on a machine". THAT IS NO LONGER TRUE — `POST /v1/agents/coding` starts
11
+ // one and answers the session to watch it in, which is the row the working view
12
+ // already renders.
13
+ //
14
+ // WHERE IT RUNS IS THE ONE CHOICE, and it is a field rather than a fork:
15
+ //
16
+ // no target the cloud's own sandbox — what a browser gets, because a
17
+ // browser has no machine to offer
18
+ // a target a machine this org has claimed, routed by id
19
+ //
20
+ // Nothing here reads the user agent to decide. The web sends no target because
21
+ // it HAS none; a surface running on a claimed machine sends that machine's id.
22
+ // One endpoint, one field, and the answer comes back rather than being assumed —
23
+ // `routed` says whether a machine actually took it, so asking for one and being
24
+ // given a sandbox is reported instead of drawn as success.
25
+ import { useCallback, useEffect, useState } from 'react';
26
+ import { Text, XStack, YStack } from '@hanzo/ui';
27
+ import { Composer } from '@hanzo/ui/chat';
28
+ import { useOpen } from './open.js';
29
+ import { CodingRefusal, dispatchable, listTargets, startCoding, } from './lib/coding.js';
30
+ import { BAD } from './lib/mix.js';
31
+ /** A field of the start row: 32px, outlined, in the row's own ink. */
32
+ const FIELD = {
33
+ height: 32,
34
+ rounded: 8,
35
+ borderWidth: 1,
36
+ borderColor: 'var(--white-10)',
37
+ bg: 'transparent',
38
+ color: 'inherit',
39
+ fontSize: '$2',
40
+ };
41
+ const SOFT = '$soft';
42
+ /** The sandbox is a destination like any other, and it is the default one. */
43
+ const SANDBOX = '';
44
+ export function Start() {
45
+ const { openSession } = useOpen();
46
+ const [prompt, setPrompt] = useState('');
47
+ const [repo, setRepo] = useState('');
48
+ const [where, setWhere] = useState(SANDBOX);
49
+ const [targets, setTargets] = useState([]);
50
+ const [busy, setBusy] = useState(false);
51
+ const [tool, setTool] = useState('dev');
52
+ const [err, setErr] = useState(null);
53
+ // The machines this org has claimed. An empty list is an ANSWER — every run
54
+ // goes to a sandbox — so it is not an error and draws no warning. Only a
55
+ // refusal is reported, and even then the composer stays usable: a sandbox run
56
+ // needs no target, so being unable to list machines must not stop a send.
57
+ useEffect(() => {
58
+ let live = true;
59
+ listTargets()
60
+ .then((t) => live && setTargets(t.filter(dispatchable)))
61
+ .catch(() => {
62
+ /* Listing is an enhancement here; the sandbox needs nothing from it. */
63
+ });
64
+ return () => {
65
+ live = false;
66
+ };
67
+ }, []);
68
+ const send = useCallback(async () => {
69
+ const task = prompt.trim();
70
+ if (!task || busy)
71
+ return;
72
+ setBusy(true);
73
+ setErr(null);
74
+ try {
75
+ const run = await startCoding({
76
+ prompt: task,
77
+ repo: repo.trim() || undefined,
78
+ tool: tool || 'dev',
79
+ targetId: where || undefined,
80
+ });
81
+ setPrompt('');
82
+ // The run is ACCEPTED, not finished. Opening its session is what puts the
83
+ // reader where it narrates itself, which is the view this pane replaces.
84
+ openSession(run.sessionId);
85
+ }
86
+ catch (e) {
87
+ setErr(e instanceof CodingRefusal ? e.message : 'Could not start the run');
88
+ }
89
+ finally {
90
+ setBusy(false);
91
+ }
92
+ }, [prompt, repo, tool, where, busy, openSession]);
93
+ return (_jsxs(YStack, { width: "100%", gap: "$2", children: [_jsx(Composer, { value: prompt, onChange: setPrompt, onSend: () => void send(), busy: busy, placeholder: "Describe what to build\u2026", label: "Describe what to build" }), _jsxs(XStack, { gap: "$2", items: "center", flexWrap: "wrap", children: [_jsxs(Text, { render: _jsx("select", { value: tool, onChange: (e) => setTool(e.currentTarget.value), "aria-label": "Agent Harness" }), ...FIELD, px: 8, children: [_jsx("option", { value: "dev", children: "Dev" }), _jsx("option", { value: "claude", children: "Claude" }), _jsx("option", { value: "codex", children: "Codex" }), _jsx("option", { value: "python", children: "Python" })] }), _jsx(Text, { render: _jsx("input", { value: repo, onChange: (e) => setRepo(e.currentTarget.value), placeholder: "owner/repo (optional)", "aria-label": "Repository" }), ...FIELD, flex: 1, minW: 160, px: 10, outlineStyle: "none" }), targets.length > 0 ? (_jsxs(Text, { render: _jsx("select", { value: where, onChange: (e) => setWhere(e.currentTarget.value), "aria-label": "Where to run" }), ...FIELD, px: 8, children: [_jsx("option", { value: SANDBOX, children: "Sandbox" }), targets.map((t) => (_jsxs("option", { value: t.id, children: [t.label || t.host || t.id, t.capacity ? ` — ${t.capacity}` : ''] }, t.id)))] })) : null] }), err ? (_jsx(Text, { role: "alert", fontSize: "$2", color: BAD, children: err })) : (_jsx(Text, { fontSize: "$2", color: SOFT, children: targets.length > 0 && where !== SANDBOX
94
+ ? 'Runs on your machine.'
95
+ : 'Runs in an isolated sandbox with Free AI / Enso.' }))] }));
96
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The strip across the top of every page while a SuperAdmin is inside an
3
+ * organization that is not theirs: which one, as whom, and the way out. It reads
4
+ * the token's `assumed` claim, so it is there exactly as long as IAM's re-scoped
5
+ * token is.
6
+ */
7
+ export declare function SupportBanner(): import("react").JSX.Element;
8
+ /**
9
+ * Every organization, for a SuperAdmin to step into. The list and the search are
10
+ * IAM's, a page at a time; picking one asks IAM to step in.
11
+ */
12
+ export declare function SupportPicker({ onClose }: {
13
+ onClose: () => void;
14
+ }): import("react").JSX.Element;
@@ -0,0 +1,122 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ // Support mode: a SuperAdmin steps into any organization to see what it sees.
4
+ //
5
+ // Everything here is IAM's. The list is IAM's organization list, which answers a
6
+ // SuperAdmin every organization and anyone else only their own; the step in and
7
+ // the step out are IAM's `assume` and `release`, which answer the operator's own
8
+ // token re-scoped and record each step in the organization's audit log
9
+ // (lib/auth/session.ts `support`). This file only draws them, and only for the
10
+ // person IAM signs as a SuperAdmin (`superAdmin`); nothing here decides who may.
11
+ import { useCallback, useEffect, useRef, useState } from 'react';
12
+ import { Text, View, XStack, YStack } from '@hanzo/gui';
13
+ import { Button } from '@hanzo/ui';
14
+ import { X } from 'lucide-react';
15
+ import { useHydrated } from './lib/hydrated.js';
16
+ import { api } from './lib/api.js';
17
+ import { abandon, assumed, bearer, named, support } from './lib/session.js';
18
+ import { BAD, mix } from './lib/mix.js';
19
+ /** The banner rides above the room's sheets, the picker between them. gui carries a `calc()` to the page unchanged;
20
+ * its types only name custom properties. */
21
+ const BANNER = 'calc(var(--z-modal) + 30)';
22
+ const DIALOG = 'calc(var(--z-modal) + 20)';
23
+ const SCRIM = mix('var(--pure-black)', 28, 'srgb');
24
+ const muted = 'var(--muted-foreground)';
25
+ /** A page of IAM's organization list: `q` narrows it, `cursor` continues it. */
26
+ async function page(q, cursor) {
27
+ const query = new URLSearchParams({ limit: '20' });
28
+ if (q.trim())
29
+ query.set('q', q.trim());
30
+ if (cursor)
31
+ query.set('cursor', cursor);
32
+ const res = await fetch(`${api()}/v1/iam/organizations?${query}`, {
33
+ headers: { Authorization: `Bearer ${bearer() ?? ''}` },
34
+ });
35
+ if (!res.ok)
36
+ throw new Error(`IAM answered ${res.status}.`);
37
+ return (await res.json());
38
+ }
39
+ /**
40
+ * The strip across the top of every page while a SuperAdmin is inside an
41
+ * organization that is not theirs: which one, as whom, and the way out. It reads
42
+ * the token's `assumed` claim, so it is there exactly as long as IAM's re-scoped
43
+ * token is.
44
+ */
45
+ export function SupportBanner() {
46
+ const hydrated = useHydrated();
47
+ const [org, setOrg] = useState(null);
48
+ const [who, setWho] = useState('');
49
+ const [busy, setBusy] = useState(false);
50
+ useEffect(() => {
51
+ if (!hydrated)
52
+ return;
53
+ setOrg(assumed());
54
+ setWho(named()?.email || named()?.name || '');
55
+ }, [hydrated]);
56
+ if (!org)
57
+ return null;
58
+ // Leaving asks IAM to release. When IAM cannot answer, the way out is the
59
+ // person's own session: the SDK's next refresh assumes nothing.
60
+ const leave = () => {
61
+ setBusy(true);
62
+ support(null).catch(() => abandon());
63
+ };
64
+ return (_jsxs(XStack, { role: "status", "aria-label": "Support mode", position: "fixed", t: 52, l: "50%", x: "-50%", maxW: "calc(100vw - 32px)", z: BANNER, items: "center", justify: "center", gap: 12, flexWrap: "wrap", pt: 6, pr: 8, pb: 6, pl: 16, rounded: 999, bg: "var(--foreground)", children: [_jsxs(Text, { fontSize: "$2", color: "var(--background)", children: ["Support mode: you are in ", _jsx("strong", { children: org }), who ? ` as ${who}` : '', ". IAM records it under your name."] }), _jsx(Button, { size: "sm", onClick: leave, disabled: busy, children: busy ? 'Leaving' : 'Leave' })] }));
65
+ }
66
+ /**
67
+ * Every organization, for a SuperAdmin to step into. The list and the search are
68
+ * IAM's, a page at a time; picking one asks IAM to step in.
69
+ */
70
+ export function SupportPicker({ onClose }) {
71
+ const [q, setQ] = useState('');
72
+ const [rows, setRows] = useState([]);
73
+ const [cursor, setCursor] = useState('');
74
+ const [loading, setLoading] = useState(false);
75
+ const [refused, setRefused] = useState(null);
76
+ const [stepping, setStepping] = useState(null);
77
+ // Only the newest read lands: a search typed while a page is in flight
78
+ // supersedes it, so a slow answer to an old query never overwrites the new one.
79
+ const asked = useRef(0);
80
+ const load = useCallback(async (query, from) => {
81
+ const mine = ++asked.current;
82
+ setLoading(true);
83
+ setRefused(null);
84
+ try {
85
+ const next = await page(query, from);
86
+ if (mine !== asked.current)
87
+ return;
88
+ setRows((had) => {
89
+ // IAM's cursor is a position, so an org created mid-walk can shift a row
90
+ // across a page edge; each org is kept once.
91
+ const all = from ? [...had, ...(next.organizations ?? [])] : next.organizations ?? [];
92
+ return all.filter((o, i) => all.findIndex((x) => x.name === o.name) === i);
93
+ });
94
+ setCursor(next.cursor ?? '');
95
+ }
96
+ catch (e) {
97
+ if (mine === asked.current)
98
+ setRefused(e instanceof Error ? e.message : 'The list did not load.');
99
+ }
100
+ finally {
101
+ if (mine === asked.current)
102
+ setLoading(false);
103
+ }
104
+ }, []);
105
+ useEffect(() => {
106
+ const id = setTimeout(() => void load(q, ''), 250);
107
+ return () => clearTimeout(id);
108
+ }, [q, load]);
109
+ const enter = (org) => {
110
+ setStepping(org);
111
+ setRefused(null);
112
+ support(org).catch((e) => {
113
+ setRefused(e instanceof Error ? e.message : 'IAM did not step in.');
114
+ setStepping(null);
115
+ });
116
+ };
117
+ return (_jsx(YStack, { role: "dialog", "aria-modal": true, "aria-label": "All organizations", onKeyDown: (e) => {
118
+ // gui types the native event; on the web it is the DOM's, which carries the key.
119
+ if ('key' in e && e.key === 'Escape')
120
+ onClose();
121
+ }, position: "fixed", inset: 0, z: DIALOG, bg: SCRIM, overflowY: "auto", py: "6vh", px: 16, onClick: onClose, children: _jsxs(YStack, { onClick: (e) => e.stopPropagation(), width: "100%", maxW: 560, mx: "auto", bg: "var(--background)", rounded: 16, pt: 24, px: 24, pb: 16, children: [_jsxs(XStack, { items: "flex-start", justify: "space-between", gap: 12, children: [_jsxs(YStack, { shrink: 1, minW: "auto", children: [_jsx(Text, { render: "h2", fontSize: "$7", fontWeight: "500", color: "var(--text-primary)", children: "All organizations" }), _jsx(Text, { render: "p", mt: 6, fontSize: "$3", color: muted, lineHeight: 21, children: "Support mode. Step into any organization to see its chats, agents and billing as it does. IAM records every step under your name." })] }), _jsx(Button, { variant: "ghost", size: "icon-sm", mr: -8, onClick: onClose, "aria-label": "Close", children: _jsx(X, { size: 16, "aria-hidden": true }) })] }), _jsx(Text, { render: _jsx("input", { value: q, onChange: (e) => setQ(e.target.value), placeholder: "Find an organization", "aria-label": "Find an organization", autoFocus: true }), width: "100%", mt: 16, fontSize: "$3", px: 12, py: 10, rounded: 10, borderWidth: 1, borderColor: "var(--border)", bg: "var(--background)", color: "var(--foreground)" }), _jsxs(View, { role: "list", "aria-label": "Organizations", mt: 10, display: "grid", gap: 2, children: [rows.map((o) => (_jsxs(XStack, { render: "button", role: "listitem", onClick: () => enter(o.name), disabled: stepping !== null, items: "baseline", justify: "space-between", gap: 12, width: "100%", px: 12, py: 10, rounded: 10, borderWidth: 0, bg: "transparent", cursor: "pointer", children: [_jsx(Text, { fontSize: "$3", fontWeight: "500", color: "var(--foreground)", text: "left", children: o.displayName || o.name }), _jsx(Text, { fontSize: "$2", color: muted, text: "left", children: stepping === o.name ? 'Stepping in' : o.name })] }, o.name))), !loading && rows.length === 0 && !refused ? (_jsx(Text, { render: "p", mx: 12, my: 8, fontSize: "$3", color: muted, children: "No organization matches." })) : null] }), refused ? (_jsx(Text, { render: "p", mx: 12, my: 8, fontSize: "$3", color: BAD, children: refused })) : null, cursor ? (_jsx(Button, { self: "flex-start", mt: 8, onClick: () => void load(q, cursor), disabled: loading, children: loading ? 'Loading' : 'More' })) : null] }) }));
122
+ }
package/dist/Talk.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import { type TeamRoom } from './rooms.js';
2
+ export declare function Talk({ room }: {
3
+ room: TeamRoom;
4
+ }): import("react").JSX.Element;
package/dist/Talk.js ADDED
@@ -0,0 +1,107 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ // A room, read and answered.
4
+ //
5
+ // The org's rooms live on /home: the rail lists them, this pane reads one, and
6
+ // the composer answers in it. It draws the same three parts every other room on
7
+ // this site draws — the header, the transcript, the one composer — from the
8
+ // same components, so a room is not a second design.
9
+ //
10
+ // WHO SAID IT is the roster's answer (`roster.ts`). A message carries the
11
+ // account that wrote it, and the name and face beside each row are looked up
12
+ // there, once, for people and agents alike; an account the roster lacks is
13
+ // printed as the id it is. The transcript is polled by `rooms.ts`, so an answer
14
+ // — a teammate's or an agent's — lands without a reload.
15
+ //
16
+ // WHAT YOU SAY IS DRAWN AT ONCE, in the quiet ink and without a stamp, and it
17
+ // stays only if the platform keeps it: the accepted row replaces it, a refusal
18
+ // drops it and hands the words back to the field. Nothing the platform did not
19
+ // accept is on screen for longer than one round trip.
20
+ import { useRef, useState } from 'react';
21
+ import { ChannelHeader } from '@hanzo/ui/agents';
22
+ import { Composer } from '@hanzo/ui/chat';
23
+ import { Box, Text, XStack, YStack } from '@hanzo/ui';
24
+ import { Users } from 'lucide-react';
25
+ import { useIam } from '@hanzo/iam/react';
26
+ import { Face } from './cast.js';
27
+ import { SpeakButton } from './speech.js';
28
+ import { say } from './failure.js';
29
+ import { useOpen } from './open.js';
30
+ import { Beside } from './Shell.js';
31
+ import { memberOf, useMe, useRoster } from './roster.js';
32
+ import { useRoomMessages } from './rooms.js';
33
+ import { BAD } from './lib/mix.js';
34
+ /** The clock a transcript reads by: the time for today, the date before that. */
35
+ function when(ms) {
36
+ const then = new Date(ms);
37
+ const sameDay = new Date().toDateString() === then.toDateString();
38
+ return then.toLocaleString(undefined, sameDay ? { hour: 'numeric', minute: '2-digit' } : { month: 'short', day: 'numeric' });
39
+ }
40
+ /**
41
+ * WHAT A ROOM IS CALLED. A channel carries a name; a direct message does not —
42
+ * its members are its name — so one falls back to the other rather than drawing
43
+ * an empty header. The sigil is how a client DRAWS a room and is not stored, so
44
+ * it is added here and only where there is a name to put it on.
45
+ */
46
+ const titleOf = (room) => room.name ? `#${room.name}` : room.direct ? 'Direct message' : 'Room';
47
+ /** Adds `@name ` to a draft, after a space where the draft needs one. */
48
+ const mention = (draft, name) => `${draft && !/\s$/.test(draft) ? `${draft} ` : draft}@${name} `;
49
+ export function Talk({ room }) {
50
+ const { messages, failed, send } = useRoomMessages(room.id, room.space);
51
+ const { members, wrong } = useRoster();
52
+ const me = useMe();
53
+ const { aside, showAside } = useOpen();
54
+ const [draft, setDraft] = useState('');
55
+ const [pending, setPending] = useState([]);
56
+ const [refused, setRefused] = useState(null);
57
+ const minted = useRef(0);
58
+ const agents = (members ?? []).filter((one) => one.agent);
59
+ const mine = me ? memberOf(members, me) : undefined;
60
+ const speak = async () => {
61
+ const text = draft.trim();
62
+ if (!text)
63
+ return;
64
+ const key = ++minted.current;
65
+ setDraft('');
66
+ setRefused(null);
67
+ setPending((held) => [...held, { key, text }]);
68
+ try {
69
+ await send(text);
70
+ }
71
+ catch (e) {
72
+ setDraft(text);
73
+ setRefused(say(e, 'this room', 'save'));
74
+ }
75
+ finally {
76
+ setPending((held) => held.filter((one) => one.key !== key));
77
+ }
78
+ };
79
+ const name = (of) => setDraft((held) => mention(held, of));
80
+ return (_jsxs(YStack, { flex: 1, minH: 0, children: [_jsxs(ChannelHeader, { name: titleOf(room), children: [room.topic ? (_jsx(Text, { fontSize: "$2", color: "$soft", numberOfLines: 1, children: room.topic })) : null, _jsx(Box, { render: "button", "aria-label": aside === 'roster' ? 'Hide directory & roster' : 'Show directory & roster', "aria-pressed": aside === 'roster', onClick: () => showAside(aside === 'roster' ? null : 'roster'), ml: "auto", p: "$2", rounded: "$3", hoverStyle: { bg: '$hover' }, children: _jsx(Users, { size: 16, "aria-hidden": true }) })] }), _jsxs(YStack, { flex: 1, minH: 0, overflow: "scroll", p: "$4", gap: "$3", children: [failed ? (_jsx(Text, { fontSize: "$3", color: "$soft", children: say(failed, 'this room') })) : messages === null ? (_jsx(Text, { fontSize: "$3", color: "$soft", children: "Reading the room." })) : messages.length === 0 && pending.length === 0 ? (_jsx(Text, { fontSize: "$3", color: "$soft", children: "Nothing has been said here yet." })) : (messages.map((one) => (_jsx(Line, { member: memberOf(members, one.author), fallback: one.author, stamp: when(one.createdOn), text: one.text }, one.id)))), pending
81
+ .filter((one) => !messages?.some((said) => said.author === me && said.text === one.text))
82
+ .map((one) => (_jsx(Line, { member: mine, fallback: "You", text: one.text, soft: true }, `pending-${one.key}`)))] }), _jsxs(YStack, { p: "$3", gap: "$2", borderTopWidth: 1, borderColor: "$borderColor", children: [_jsx(Composer, { value: draft, onChange: setDraft, onSend: () => void speak(), placeholder: `Message ${titleOf(room)}`, label: `Message ${titleOf(room)}`, hint: agents.length ? 'Mention an agent with @name and it answers.' : undefined, children: agents.length ? (_jsx(XStack, { gap: "$1", flexWrap: "wrap", shrink: 1, minW: 0, children: agents.map((one) => (_jsx(Box, { render: "button", "aria-label": `Mention ${one.name}`, onClick: () => name(one.name), px: "$2", py: "$1", rounded: "$10", hoverStyle: { bg: '$hover' }, children: _jsxs(Text, { fontSize: "$2", color: "$soft", children: ["@", one.name] }) }, one.id))) })) : null }), refused ? (_jsx(Text, { fontSize: "$2", color: BAD, children: refused })) : null] }), _jsx(Beside, { children: aside === 'roster' ? _jsx(Roster, { members: members, wrong: wrong, onMention: name }) : null })] }));
83
+ }
84
+ /** One thing said: a face, a name, the stamp where the platform gave one. */
85
+ function Line({ member, fallback, stamp, text, soft, }) {
86
+ const ink = soft ? '$soft' : '$ink';
87
+ return (_jsxs(XStack, { gap: "$3", items: "flex-start", children: [_jsx(Face, { src: member?.avatar, name: member?.name ?? fallback, size: 32, person: true }), _jsxs(YStack, { flex: 1, minW: 0, gap: "$1", children: [_jsxs(XStack, { gap: "$2", items: "baseline", children: [_jsx(Text, { fontSize: "$3", fontWeight: "500", color: ink, numberOfLines: 1, children: member?.name ?? fallback }), member?.agent ? (_jsx(Box, { px: "$1.5", py: "$0.5", rounded: "$2", bg: "$hover", children: _jsx(Text, { fontSize: "$1", color: "$soft", fontWeight: "600", children: "Agent" }) })) : null, stamp ? (_jsx(Text, { fontSize: "$1", color: "$soft", children: stamp })) : null] }), _jsx(Text, { fontSize: "$3", color: ink, children: text }), _jsx(XStack, { mt: "$1", children: _jsx(SpeakButton, { text: text, agentName: member?.name ?? fallback, compact: true }) })] })] }));
88
+ }
89
+ /**
90
+ * Who is here, beside the room. An agent is pressable — pressing it names it in
91
+ * the draft, which is how it is asked something — and a person is a person:
92
+ * IAM publishes no route that messages one, so the row identifies rather than
93
+ * opens.
94
+ */
95
+ function Roster({ members, wrong, onMention, }) {
96
+ const { isAuthenticated } = useIam();
97
+ const line = !isAuthenticated
98
+ ? 'Sign in to see who is here.'
99
+ : wrong
100
+ ? say(wrong, "this org's roster")
101
+ : members === null
102
+ ? 'Reading the roster.'
103
+ : members.length < 2
104
+ ? 'Just you.'
105
+ : null;
106
+ return (_jsxs(YStack, { children: [_jsx(XStack, { height: 48, shrink: 0, items: "center", px: "$3", borderBottomWidth: 1, borderColor: "$borderColor", children: _jsx(Text, { fontSize: "$3", fontWeight: "600", color: "$ink", children: "Roster" }) }), line ? (_jsx(Text, { p: "$3", fontSize: "$2", color: "$soft", children: line })) : (_jsx(YStack, { p: "$2", gap: "$0.5", children: (members ?? []).map((one) => (_jsx(Box, { render: one.agent ? 'button' : 'div', onClick: one.agent ? () => onMention(one.name) : undefined, "aria-label": one.agent ? `Mention ${one.name}` : undefined, width: "100%", minW: 0, items: "flex-start", px: "$2", py: "$1.5", rounded: "$3", hoverStyle: one.agent ? { bg: '$hover' } : undefined, children: _jsxs(XStack, { items: "center", gap: "$2", width: "100%", children: [_jsx(Face, { src: one.avatar, name: one.name, size: 24, person: true }), _jsx(Text, { fontSize: "$2", color: "$ink", numberOfLines: 1, flex: 1, minW: 0, text: "left", children: one.name }), one.agent ? (_jsx(Text, { fontSize: "$1", color: "$soft", children: "agent" })) : null] }) }, one.id))) }))] }));
107
+ }
@@ -0,0 +1,11 @@
1
+ /** What the reader reached for: a model by its name, and the checkout a refusal named. */
2
+ export interface Ask {
3
+ model?: string;
4
+ href?: string;
5
+ }
6
+ /** Whether a signed-in reader's organization is on the Free plan. A tier not yet read, or refused, is not Free. */
7
+ export declare function useFree(signedIn: boolean): boolean;
8
+ export declare function Upgrade({ ask, onClose }: {
9
+ ask: Ask | null;
10
+ onClose: () => void;
11
+ }): import("react").JSX.Element;
@@ -0,0 +1,75 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ // The ask to upgrade, for a reader on the Free plan.
4
+ //
5
+ // A Free reader who reaches for something Free does not include is shown the
6
+ // first personal plan that charges (`usePlan('personal')`), priced by the
7
+ // catalog for a month or for a year, with checkout at hanzo.ai/pay. `useFree`
8
+ // says who is on Free; `Upgrade` draws the ask it is handed and nothing when it
9
+ // is handed none. A refusal's ask is read by `planRequired` in `failure.ts`.
10
+ import { useEffect, useState } from 'react';
11
+ import { Blocks, Brain, Gauge, ListChecks, Sparkles, X } from 'lucide-react';
12
+ import { Anchor, Box, Dialog, DialogContent, DialogTitle, Text, ToggleGroup, ToggleGroupItem, XStack, YStack } from '@hanzo/ui';
13
+ import { track } from './lib/tags.js';
14
+ import { org } from './lib/session.js';
15
+ import { useTier } from './lib/tier.js';
16
+ import { charge, planCheckoutUrl, quote, saving, term, usePlan } from './lib/plans.js';
17
+ import { site } from './where.js';
18
+ /** The free rung's name in the billing catalog. */
19
+ const FREE = 'free';
20
+ /** Whether a signed-in reader's organization is on the Free plan. A tier not yet read, or refused, is not Free. */
21
+ export function useFree(signedIn) {
22
+ const { tier } = useTier(signedIn, org());
23
+ return signedIn && tier?.tier?.name === FREE;
24
+ }
25
+ const ROWS = [
26
+ [Gauge, 'Higher usage limits'],
27
+ [Sparkles, 'Access to all Hanzo models'],
28
+ [Brain, 'Memory that carries across conversations'],
29
+ [Blocks, 'Build and prototype with Hanzo Studio'],
30
+ [ListChecks, 'Power through tasks with Hanzo Team'],
31
+ ];
32
+ /** The page checkout returns to: this one, query and all. */
33
+ function here() {
34
+ if (typeof window === 'undefined')
35
+ return undefined;
36
+ const { origin, pathname, search } = window.location;
37
+ return `${origin}${pathname}${search}`;
38
+ }
39
+ export function Upgrade({ ask, onClose }) {
40
+ const { plan } = usePlan('personal');
41
+ const [cycle, setCycle] = useState('month');
42
+ const shown = ask !== null && plan !== null;
43
+ // The plan is shown to a reader who reached for it: GA4's view_item.
44
+ useEffect(() => {
45
+ if (shown && plan)
46
+ track('product_viewed', {
47
+ currency: 'USD',
48
+ value: charge(plan, 'month'),
49
+ items: [{ item_id: plan.id, item_name: plan.name }],
50
+ });
51
+ // eslint-disable-next-line react-hooks/exhaustive-deps
52
+ }, [shown]);
53
+ if (!plan)
54
+ return null;
55
+ const save = saving(plan);
56
+ const interval = save === null ? 'month' : cycle;
57
+ const label = `Upgrade to ${plan.name} for ${quote(plan, interval)}`;
58
+ const href = term(ask?.href ?? planCheckoutUrl(plan, here()), interval);
59
+ const buy = () => {
60
+ const items = [{ item_id: plan.id, item_name: plan.name }];
61
+ track('plan_clicked', { plan: plan.id, interval, cta: label, items });
62
+ track('checkout_started', { currency: 'USD', value: charge(plan, interval), items, interval });
63
+ };
64
+ return (_jsx(Dialog, { open: ask !== null, onOpenChange: (open) => (open ? undefined : onClose()), children: _jsxs(DialogContent, { showCloseButton: false, width: "calc(100vw - 32px)", maxW: 440, bg: "var(--popover)", rounded: "$9", p: "$5", gap: "$5", $sm: { p: '$6' }, children: [_jsx(Box, { render: "button", "aria-label": "Close", onClick: onClose, position: "absolute", t: "$4", r: "$4", p: "$1.5", rounded: "$2", bg: "transparent", borderWidth: 0, cursor: "pointer", hoverStyle: { bg: '$hover' }, children: _jsx(X, { size: 18, "aria-hidden": true, color: "var(--soft, var(--muted-foreground))" }) }), _jsx(DialogTitle
65
+ // inline-style: design's serif face. gui names Zen and Zen Mono only; a family it cannot name resets the rung to 15px.
66
+ , {
67
+ // inline-style: design's serif face. gui names Zen and Zen Mono only; a family it cannot name resets the rung to 15px.
68
+ style: { fontFamily: 'var(--font-serif)' }, fontSize: "$8", lineHeight: "$9", fontWeight: "400", color: "$ink", pr: "$6", children: ask?.model ? `Use ${ask.model} with Hanzo ${plan.name}` : `Upgrade to Hanzo ${plan.name}` }), _jsx(YStack, { render: "ul", gap: "$3.5", m: 0, p: 0, children: ROWS.map(([Icon, line]) => (_jsxs(XStack, { render: "li", items: "center", gap: "$3", children: [_jsx(Icon, { size: 18, "aria-hidden": true, color: "var(--soft, var(--muted-foreground))" }), _jsx(Text, { fontSize: "$4", color: "$ink", children: line })] }, line))) }), save === null ? null : (_jsx(ToggleGroup, { type: "single", value: cycle, onValueChange: (v) => {
69
+ if (v === 'month' || v === 'year')
70
+ setCycle(v);
71
+ }, disableDeactivation: true, "aria-label": "Billing term", ...{ width: '100%', p: 4, rounded: 9999, bg: 'var(--surface-2)' }, children: [
72
+ ['month', 'Monthly'],
73
+ ['year', `Yearly · Save ${save}%`],
74
+ ].map(([value, name]) => (_jsx(ToggleGroupItem, { value: value, variant: "outline", flex: 1, rounded: "$10", borderColor: "transparent", children: name }, value))) })), _jsxs(YStack, { gap: "$3", children: [_jsx(Anchor, { href: href, onPress: buy, "data-ga": true, bg: "var(--primary)", color: "var(--primary-foreground)", rounded: "$4", py: "$3", px: "$4", fontSize: "$4", fontWeight: "500", text: "center", textDecorationLine: "none", hoverStyle: { bg: 'var(--primary-hover)' }, children: label }), _jsxs(XStack, { justify: "center", gap: "$5", children: [_jsx(Anchor, { href: site("/pricing"), fontSize: "$3", color: "$soft", hoverStyle: { color: '$ink' }, children: "See all plans" }), _jsx(Box, { render: "button", onClick: onClose, bg: "transparent", borderWidth: 0, p: 0, cursor: "pointer", children: _jsx(Text, { fontSize: "$3", color: "$soft", hoverStyle: { color: '$ink' }, children: "Not now" }) })] })] }), _jsx(Text, { fontSize: "$1", color: "$soft", text: "center", children: "Prices and plans are subject to change at Hanzo's discretion." })] }) }));
75
+ }
@@ -0,0 +1 @@
1
+ export declare function Usage(): import("react").JSX.Element;
package/dist/Usage.js ADDED
@@ -0,0 +1,21 @@
1
+ 'use client';
2
+ import { jsxs as _jsxs } from "react/jsx-runtime";
3
+ // Free's usage, said where it is spent. Free is limited, and what it spends
4
+ // comes from one pool every free user shares, so the line says both, and what
5
+ // is left of today's when billing states a day window for the plan (the tier's
6
+ // `windows`, lib/hanzo/tier.ts). It renders nothing for any other plan, or
7
+ // while the plan is unread.
8
+ import { useIam } from '@hanzo/iam/react';
9
+ import { Text } from '@hanzo/ui';
10
+ import { org } from './lib/session.js';
11
+ import { useTier } from './lib/tier.js';
12
+ /** The free rung's name in the billing catalog. */
13
+ const FREE = 'free';
14
+ export function Usage() {
15
+ const { isAuthenticated } = useIam();
16
+ const { tier } = useTier(isAuthenticated, org());
17
+ if (tier?.tier?.name !== FREE)
18
+ return null;
19
+ const today = tier.windows?.find((w) => /day/i.test(w.span));
20
+ return (_jsxs(Text, { role: "status", fontSize: "$1", color: "$soft", text: "center", children: ["Free \u00B7 limited, shared pool", today ? ` · ${today.remaining} of ${today.limit} left today` : ''] }));
21
+ }
package/dist/Vibe.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare function VibeWorkspace(): React.JSX.Element;