@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
package/dist/card.d.ts ADDED
@@ -0,0 +1,52 @@
1
+ import type { ReactNode } from 'react';
2
+ /** Primary text. #e5e5e5. */
3
+ export declare const INK = "$ink";
4
+ /** Secondary text. rgb(171,171,171) — 8.6:1 on the page ground, past AA. */
5
+ export declare const SOFT = "$soft";
6
+ /** The fill a chosen control wears. `$hover` is the one it wears on approach. */
7
+ export declare const CHOSEN = "$raised";
8
+ /**
9
+ * One card.
10
+ *
11
+ * NO `height="100%"` AND NO `flex={1}` INSIDE. A grid already stretches a cell
12
+ * to its row; `flex: 1 1 0` on a child is a ZERO basis, so a column parent
13
+ * measures that child as nothing and the card closes above its own rows.
14
+ * Measured at 390: a card was 54px tall around 80px of content and printed
15
+ * straight over the one below it.
16
+ */
17
+ export declare function Card({ kind, icon, title, action, children, }: {
18
+ /** What this card is, for a test to find it by. */
19
+ kind: string;
20
+ icon: ReactNode;
21
+ title: string;
22
+ /**
23
+ * A control at the far end of the heading — the `+` that adds to what the
24
+ * card lists.
25
+ *
26
+ * IN THE HEADING RATHER THAN BESIDE THE CARD, because what it adds to is the
27
+ * card's own list, and a control that lives outside the box it acts on has to
28
+ * be labelled to say which box it means. `flex={1}` on the title is what
29
+ * pushes it right: a `justifyContent` here would also spread the icon away
30
+ * from the word it belongs to.
31
+ */
32
+ action?: ReactNode;
33
+ children: ReactNode;
34
+ }): import("react").JSX.Element;
35
+ /** One line in a card: a word, and where it goes. Neither, and it is a word. */
36
+ export declare function Row({ children, icon, href, onPress, }: {
37
+ children: ReactNode;
38
+ icon?: ReactNode;
39
+ href?: string;
40
+ onPress?: () => void;
41
+ }): import("react").JSX.Element;
42
+ /** The honest nothing. `null` is unknown and never renders as none. */
43
+ export declare function Quiet({ children }: {
44
+ children: ReactNode;
45
+ }): import("react").JSX.Element;
46
+ /** The card's action, at its foot — `margin-top: auto` and not a flex spacer,
47
+ * which is what the note on `Card` is about. An address, or a press. */
48
+ export declare function Go({ href, onPress, children, }: {
49
+ href?: string;
50
+ onPress?: () => void;
51
+ children: ReactNode;
52
+ }): import("react").JSX.Element;
package/dist/card.js ADDED
@@ -0,0 +1,52 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { ArrowRight } from 'lucide-react';
4
+ import { Box, Text, XStack, YStack } from '@hanzo/ui';
5
+ import { tap } from './lib/tap.js';
6
+ /** Primary text. #e5e5e5. */
7
+ export const INK = '$ink';
8
+ /** Secondary text. rgb(171,171,171) — 8.6:1 on the page ground, past AA. */
9
+ export const SOFT = '$soft';
10
+ /** The fill a chosen control wears. `$hover` is the one it wears on approach. */
11
+ export const CHOSEN = '$raised';
12
+ /**
13
+ * One card.
14
+ *
15
+ * NO `height="100%"` AND NO `flex={1}` INSIDE. A grid already stretches a cell
16
+ * to its row; `flex: 1 1 0` on a child is a ZERO basis, so a column parent
17
+ * measures that child as nothing and the card closes above its own rows.
18
+ * Measured at 390: a card was 54px tall around 80px of content and printed
19
+ * straight over the one below it.
20
+ */
21
+ export function Card({ kind, icon, title, action, children, }) {
22
+ return (_jsxs(YStack, { "data-card": kind, gap: "$2", p: "$3", borderWidth: 1, borderColor: "$borderColor", rounded: "$4", bg: "$panel", minW: 0, hoverStyle: { bg: '$hover' }, children: [_jsxs(XStack, { items: "center", gap: "$2", children: [icon, _jsx(Text, { fontSize: "$2", fontWeight: "500", color: INK, flex: 1, minW: 0, numberOfLines: 1, children: title }), action] }), children] }));
23
+ }
24
+ /** One line in a card: a word, and where it goes. Neither, and it is a word. */
25
+ export function Row({ children, icon, href, onPress, }) {
26
+ // `numberOfLines` CANNOT CLAMP WITHOUT A WIDTH TO CLAMP TO. A flex child's
27
+ // floor is its own content, so a long title pushed the row wider than the
28
+ // card and the sentence ran out through the side of it — the ellipsis never
29
+ // had an edge to appear at. The width is stated and the floor is dropped, so
30
+ // the row is exactly as wide as the card and the clamp has somewhere to bite.
31
+ const body = (_jsxs(XStack, { items: "center", gap: "$1.5", width: "100%", minW: 0, children: [icon ? (_jsx(Box, { shrink: 0, opacity: 0.8, display: "flex", items: "center", justify: "center", children: icon })) : null, _jsx(Text, { fontSize: "$2", color: SOFT, numberOfLines: 1, flex: 1, minW: 0, text: "left", children: children })] }));
32
+ if (href) {
33
+ return (_jsx(Box, { render: _jsx("a", { href: href, target: href.startsWith('http') ? '_blank' : undefined, rel: "noreferrer" }), ...tap, display: "block", minW: 0, maxW: "100%", children: body }));
34
+ }
35
+ if (onPress) {
36
+ return (_jsx(Box, { render: "button", onClick: onPress, width: "100%", minW: 0, items: "flex-start", children: body }));
37
+ }
38
+ return body;
39
+ }
40
+ /** The honest nothing. `null` is unknown and never renders as none. */
41
+ export function Quiet({ children }) {
42
+ return (_jsx(Text, { fontSize: "$2", color: SOFT, children: children }));
43
+ }
44
+ /** The card's action, at its foot — `margin-top: auto` and not a flex spacer,
45
+ * which is what the note on `Card` is about. An address, or a press. */
46
+ export function Go({ href, onPress, children, }) {
47
+ const face = (_jsxs(XStack, { items: "center", gap: "$1", children: [_jsx(Text, { fontSize: "$2", fontWeight: "500", color: INK, children: children }), _jsx(ArrowRight, { size: 12, "aria-hidden": true })] }));
48
+ if (href) {
49
+ return (_jsx(Box, { render: _jsx("a", { href: href, target: href.startsWith('http') ? '_blank' : undefined, rel: "noreferrer" }), ...tap, mt: "auto", children: face }));
50
+ }
51
+ return (_jsx(Box, { render: "button", onClick: onPress, mt: "auto", items: "flex-start", children: face }));
52
+ }
package/dist/cast.d.ts ADDED
@@ -0,0 +1,143 @@
1
+ import { type ReactNode } from 'react';
2
+ interface Role {
3
+ /**
4
+ * The likeness, under /public.
5
+ *
6
+ * OPTIONAL, because casting a part and drawing a face are two acts and the
7
+ * second one waits on somebody making a picture. A character with a voice and
8
+ * no portrait is cast — it reads its lines in the voice chosen for it — and
9
+ * wears initials until the likeness exists. The alternative is holding the
10
+ * voice back until the artwork lands, which couples one to the other for no
11
+ * reason.
12
+ */
13
+ portrait?: string;
14
+ /**
15
+ * What sits behind it.
16
+ *
17
+ * Five of the seven portraits are cut out with a transparent ground, so about
18
+ * half the disc is whatever is behind them — and on a page that follows the
19
+ * reader's theme that would be white in one and near-black in the other, with
20
+ * a dark suit or white hair disappearing into it. A named colour is the
21
+ * fix, and it is the character's own: the warm brown behind a charcoal suit, the bronze that
22
+ * lifts a black jacket. The two portraits that arrived with a ground keep it
23
+ * and name none.
24
+ */
25
+ ground?: string;
26
+ /**
27
+ * The voice the platform reads this character's replies in, by
28
+ * /v1/audio/speech name.
29
+ *
30
+ * ONE VOICE PER CHARACTER, and never two who would be in the same room. The
31
+ * service serves eleven and there are fourteen of them, so three must double
32
+ * up — the question is which three, and the answer is people who work apart:
33
+ * the five the demo puts in one call are all distinct. `echo` was reading for
34
+ * three at once, which in a call is one person doing three parts.
35
+ *
36
+ * CAST, NOT COPIED. These are the speech service's own voices, chosen for fit
37
+ * — a warm animated reader for the physicist, something low and dry for the
38
+ * founder. None of them is anybody's actual voice and none is trying to be:
39
+ * synthesising a real person saying words they never said is a different act
40
+ * from casting a part, and this is the second one.
41
+ */
42
+ voice: string;
43
+ }
44
+ /** The named crew, keyed by the persona's @-handle. */
45
+ export declare const CAST: Record<string, Role>;
46
+ /** The role for an agent name, or undefined for one nobody cast. */
47
+ export declare const roleOf: (name: string | undefined) => Role | undefined;
48
+ /** A core member's system prompt, as hanzoai/personas writes it, or undefined for anyone else. */
49
+ export declare const speaksOf: (name: string | undefined) => string | undefined;
50
+ /** Names as a sentence lists them: "Feynman", "Feynman & Einstein", "Feynman, Einstein & Vi". */
51
+ export declare const join: (names: string[]) => string;
52
+ /**
53
+ * The room's system turn.
54
+ *
55
+ * One person's brief rides as it is, under their name. Several ride together:
56
+ * who is in the room, who answers — the ones the user names, or everyone when
57
+ * nobody is named (Chat.tsx `addressed` draws the same rule) — the one rule that
58
+ * lets a reader tell them apart — each contribution opens with a name and a
59
+ * colon — and then each brief under its name. Null for a room with nobody to
60
+ * describe, which is the plain model.
61
+ */
62
+ export declare function brief(cast: {
63
+ name: string;
64
+ says: string | null;
65
+ }[]): string | null;
66
+ /**
67
+ * The line a conversation records to say who is in it. The thread keeps it as a
68
+ * system turn, so reopening the thread anywhere seats the same room. Names are
69
+ * the agents' handles, which carry no comma (the agents API's name pattern).
70
+ */
71
+ export declare const roster: (names: string[]) => string;
72
+ /** The names a recorded roster line holds, or null for a turn that is not one. */
73
+ export declare function rosterOf(content: string): string[] | null;
74
+ /**
75
+ * The system turn a room sends: the roster line, then the brief. Null for an
76
+ * empty room, which is the plain model.
77
+ */
78
+ export declare function roomTurn(names: string[], said: string | null): string | null;
79
+ /**
80
+ * A room's reply, split by who said what.
81
+ *
82
+ * A line that opens with a member's name and a colon — plain or bold — starts
83
+ * that member's part; lines before any such line belong to nobody, and a reply
84
+ * that names nobody is one part with no name on it. Inside a code fence a line
85
+ * is code, never a byline: `dev: next dev` in a YAML block stays in the block.
86
+ */
87
+ export declare function speakers(text: string, names: string[]): {
88
+ who: string | null;
89
+ text: string;
90
+ }[];
91
+ export declare const VOICES: string[];
92
+ export declare function fallbackVoice(name: string | undefined): string;
93
+ /** The voice to read a character's replies in. Always returns a valid voice. */
94
+ export declare const voiceOf: (name: string | undefined) => string;
95
+ export interface VoiceProfile {
96
+ voice: string;
97
+ pitch: number;
98
+ rate: number;
99
+ description: string;
100
+ gender: 'male' | 'female' | 'neutral';
101
+ }
102
+ export declare const VOICE_PROFILES: Record<string, VoiceProfile>;
103
+ export declare function voiceProfileOf(name: string | undefined): VoiceProfile;
104
+ export declare function fallbackMemoji(name: string | undefined): string;
105
+ /**
106
+ * One character's disc — the likeness where there is one, initials where there
107
+ * is not.
108
+ *
109
+ * It was private to the roster and drew initials only, so an agent looked the
110
+ * same in the column, the sidebar and everywhere else: like nobody. One face,
111
+ * every caller, one size prop — because the sidebar's row and the roster's
112
+ * column want the same picture at two sizes, not two pictures.
113
+ */
114
+ export declare function Face({ src, emoji, name, title, size, person, }: {
115
+ /**
116
+ * A picture this subject SAVED, which outranks the cast.
117
+ *
118
+ * A person who uploads a photograph has answered the question this file
119
+ * otherwise answers for them, and a character nobody cast has no likeness to
120
+ * lose. So the order is: what they chose, then what we cast, then initials —
121
+ * and it is stated here because it is the same order in the sidebar, the
122
+ * roster and the bar, which is why they all call this.
123
+ */
124
+ src?: string;
125
+ /**
126
+ * One glyph the subject picked instead of a picture.
127
+ *
128
+ * The platform stores at most one of avatar and emoji — `iam/pkg/schema`
129
+ * decides that at the write, precisely so no screen has to rank them — and
130
+ * both are the subject's OWN answer, so both come before ours.
131
+ */
132
+ emoji?: string;
133
+ /** Absent while a signed-in reader's profile is still on the wire. */
134
+ name?: string;
135
+ title?: string;
136
+ size?: number;
137
+ /**
138
+ * A person, not an agent: never cast. A person with no saved picture shows
139
+ * initials; a memoji is theirs only once they make one for their profile.
140
+ */
141
+ person?: boolean;
142
+ }): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal>;
143
+ export {};
package/dist/cast.js ADDED
@@ -0,0 +1,251 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ // The cast: who plays whom, and how they look and sound.
4
+ //
5
+ // Who a core member IS — their name, their part and what the model is told —
6
+ // is written once, in hanzoai/personas, and read here through team.ts; an agent
7
+ // an org made says who it is in its own row. This file is the other half, and
8
+ // only the other half: a likeness to serve and a voice to read in. An agent row
9
+ // carries neither, and it should not: a portrait is a file and a voice is a
10
+ // name passed to /v1/audio/speech.
11
+ //
12
+ // A NAME NOT IN THIS LIST IS NOT A PROBLEM. An agent falls through to a 3D
13
+ // Memoji portrait from our palette, so every agent has a distinct likeness;
14
+ // a person is never cast and shows their own picture or initials.
15
+ import { useEffect, useState } from 'react';
16
+ import { Text, Tooltip, TooltipContent, TooltipTrigger, View, XStack } from '@hanzo/ui';
17
+ import { member } from './team.js';
18
+ /** The named crew, keyed by the persona's @-handle. */
19
+ export const CAST = {
20
+ // The core team first, in the order the crew is drawn (team.ts). Des and Vi's
21
+ // grounds are their own rim light pulled to the register the crew below sits
22
+ // in: both wear black and have dark hair, so on a dark page an ungrounded
23
+ // cut-out is a floating head.
24
+ //
25
+ // Dev wears its own memoji (black ground baked in, so it fills the disc and
26
+ // needs no rim colour); hanzo coder is that same face under the house name.
27
+ dev: { portrait: '/agents/dev.png', voice: 'ash' },
28
+ des: { portrait: '/agents/des.png', ground: '#74569F', voice: 'echo' },
29
+ vi: { portrait: '/agents/vi.png', ground: '#41649F', voice: 'sage' },
30
+ antje: { portrait: '/agents/antje.jpg', voice: 'nova' },
31
+ zach: { portrait: '/agents/zach.jpg', voice: 'onyx' },
32
+ feynman: { portrait: '/agents/feynman.png', ground: '#9C7C4F', voice: 'fable' },
33
+ einstein: { portrait: '/agents/einstein.png', ground: '#7D6450', voice: 'verse' },
34
+ nora: { portrait: '/agents/nora.png', voice: 'shimmer' },
35
+ creative: { portrait: '/agents/creative.png', voice: 'ballad' },
36
+ maya: { portrait: '/agents/maya.png', voice: 'coral' },
37
+ leo: { portrait: '/agents/leo.png', voice: 'alloy' },
38
+ // THE HOUSE AGENTS, UNDER THE NAMES A READER SEES. Every entry above is keyed
39
+ // by the character; these are keyed by the part.
40
+ 'hanzo coder': { portrait: '/agents/dev.png', voice: 'ash' },
41
+ 'hanzo researcher': { portrait: '/agents/feynman.png', voice: 'fable' },
42
+ 'hanzo designer': { portrait: '/agents/des.png', voice: 'echo' },
43
+ 'hanzo devops': { voice: 'sage' },
44
+ 'hanzo quant': { voice: 'onyx' },
45
+ 'hanzo copywriter': { portrait: '/agents/creative.png', voice: 'ballad' },
46
+ 'hanzo legal & compliance': { portrait: '/agents/einstein.png', voice: 'verse' },
47
+ 'hanzo executive assistant': { portrait: '/agents/maya.png', voice: 'coral' },
48
+ 'hanzo architect': { voice: 'verse' },
49
+ 'hanzo vision': { voice: 'ballad' },
50
+ 'hanzo support': { portrait: '/agents/nora.png', voice: 'shimmer' },
51
+ };
52
+ /** The role for an agent name, or undefined for one nobody cast. */
53
+ export const roleOf = (name) => CAST[name?.toLowerCase() ?? ''];
54
+ /** A core member's system prompt, as hanzoai/personas writes it, or undefined for anyone else. */
55
+ export const speaksOf = (name) => member(name)?.instructions;
56
+ /** Names as a sentence lists them: "Feynman", "Feynman & Einstein", "Feynman, Einstein & Vi". */
57
+ export const join = (names) => names.length < 2 ? (names[0] ?? '') : `${names.slice(0, -1).join(', ')} & ${names[names.length - 1]}`;
58
+ /**
59
+ * The room's system turn.
60
+ *
61
+ * One person's brief rides as it is, under their name. Several ride together:
62
+ * who is in the room, who answers — the ones the user names, or everyone when
63
+ * nobody is named (Chat.tsx `addressed` draws the same rule) — the one rule that
64
+ * lets a reader tell them apart — each contribution opens with a name and a
65
+ * colon — and then each brief under its name. Null for a room with nobody to
66
+ * describe, which is the plain model.
67
+ */
68
+ export function brief(cast) {
69
+ if (!cast.length || cast.every((one) => !one.says))
70
+ return null;
71
+ if (cast.length === 1) {
72
+ const [one] = cast;
73
+ return /^you are\b/i.test(one.says) ? one.says : `You are ${one.name}. ${one.says}`;
74
+ }
75
+ const names = cast.map((one) => one.name);
76
+ return [
77
+ `There are ${cast.length} of you in this room with the user: ${join(names)}. When the user names some of you, those of you answer, each in turn; when the user names nobody, each of you answers. Each of you answers as yourself, in your own voice. Begin each contribution on its own line with your name and a colon, as in "${names[0]}: ...". Never write the user's lines. Who each of you is follows, one section per name.`,
78
+ ...cast.map((one) => `## ${one.name}\n\n${one.says ?? `${one.name} answers as ${one.name}.`}`),
79
+ ].join('\n\n');
80
+ }
81
+ /** The first line of a room's system turn: who is in the conversation, by name. */
82
+ const ROSTER = 'Agents in this conversation: ';
83
+ /**
84
+ * The line a conversation records to say who is in it. The thread keeps it as a
85
+ * system turn, so reopening the thread anywhere seats the same room. Names are
86
+ * the agents' handles, which carry no comma (the agents API's name pattern).
87
+ */
88
+ export const roster = (names) => `${ROSTER}${names.length ? names.join(', ') : 'none'}`;
89
+ /** The names a recorded roster line holds, or null for a turn that is not one. */
90
+ export function rosterOf(content) {
91
+ const line = content.split('\n', 1)[0];
92
+ if (!line.startsWith(ROSTER))
93
+ return null;
94
+ const names = line.slice(ROSTER.length).trim();
95
+ return names === 'none' ? [] : names.split(',').map((one) => one.trim()).filter(Boolean);
96
+ }
97
+ /**
98
+ * The system turn a room sends: the roster line, then the brief. Null for an
99
+ * empty room, which is the plain model.
100
+ */
101
+ export function roomTurn(names, said) {
102
+ if (!names.length)
103
+ return null;
104
+ return said ? `${roster(names)}\n\n${said}` : roster(names);
105
+ }
106
+ /**
107
+ * A room's reply, split by who said what.
108
+ *
109
+ * A line that opens with a member's name and a colon — plain or bold — starts
110
+ * that member's part; lines before any such line belong to nobody, and a reply
111
+ * that names nobody is one part with no name on it. Inside a code fence a line
112
+ * is code, never a byline: `dev: next dev` in a YAML block stays in the block.
113
+ */
114
+ export function speakers(text, names) {
115
+ const known = new Map(names.map((one) => [one.toLowerCase(), one]));
116
+ const out = [];
117
+ let fenced = false;
118
+ for (const line of text.split('\n')) {
119
+ const fence = /^\s*(```|~~~)/.test(line);
120
+ const m = fenced || fence ? null : /^\s*(?:\*\*|__)?([^:\n*_]{1,40}?)(?:\*\*|__)?\s*:\s?(.*)$/.exec(line);
121
+ if (fence)
122
+ fenced = !fenced;
123
+ const who = m ? known.get(m[1].trim().toLowerCase()) : undefined;
124
+ if (who && m) {
125
+ out.push({ who, text: m[2] });
126
+ continue;
127
+ }
128
+ if (!out.length)
129
+ out.push({ who: null, text: line });
130
+ else
131
+ out[out.length - 1].text += '\n' + line;
132
+ }
133
+ return out.map((one) => ({ ...one, text: one.text.trim() })).filter((one) => one.text || one.who);
134
+ }
135
+ export const VOICES = [
136
+ 'ash',
137
+ 'echo',
138
+ 'sage',
139
+ 'nova',
140
+ 'onyx',
141
+ 'fable',
142
+ 'shimmer',
143
+ 'alloy',
144
+ 'verse',
145
+ 'ballad',
146
+ 'coral',
147
+ ];
148
+ export function fallbackVoice(name) {
149
+ if (!name)
150
+ return VOICES[0];
151
+ let hash = 0;
152
+ for (let i = 0; i < name.length; i++) {
153
+ hash = (hash << 5) - hash + name.charCodeAt(i);
154
+ hash |= 0;
155
+ }
156
+ return VOICES[Math.abs(hash) % VOICES.length];
157
+ }
158
+ /** The voice to read a character's replies in. Always returns a valid voice. */
159
+ export const voiceOf = (name) => roleOf(name)?.voice || fallbackVoice(name);
160
+ export const VOICE_PROFILES = {
161
+ ash: { voice: 'ash', pitch: 1.0, rate: 1.0, description: 'Clear, direct, technical', gender: 'male' },
162
+ echo: { voice: 'echo', pitch: 1.1, rate: 1.05, description: 'Adaptive, creative, resonant', gender: 'female' },
163
+ sage: { voice: 'sage', pitch: 0.95, rate: 1.05, description: 'Authoritative, calm, systems-minded', gender: 'male' },
164
+ nova: { voice: 'nova', pitch: 1.1, rate: 1.0, description: 'Energetic, articulate, engaging', gender: 'female' },
165
+ onyx: { voice: 'onyx', pitch: 0.85, rate: 1.0, description: 'Deep, measured, quantitative', gender: 'male' },
166
+ fable: { voice: 'fable', pitch: 1.05, rate: 1.12, description: 'Warm, enthusiastic, pedagogical', gender: 'male' },
167
+ shimmer: { voice: 'shimmer', pitch: 1.1, rate: 0.98, description: 'Precise, professional, clear', gender: 'female' },
168
+ alloy: { voice: 'alloy', pitch: 0.95, rate: 1.05, description: 'Visionary, forward-leaning, focused', gender: 'male' },
169
+ verse: { voice: 'verse', pitch: 0.9, rate: 0.95, description: 'Intentional, iconic, design-first', gender: 'male' },
170
+ ballad: { voice: 'ballad', pitch: 0.92, rate: 1.0, description: 'Thoughtful, deep, expansive', gender: 'male' },
171
+ coral: { voice: 'coral', pitch: 1.15, rate: 1.05, description: 'Friendly, supportive, organized', gender: 'female' },
172
+ };
173
+ export function voiceProfileOf(name) {
174
+ const v = voiceOf(name);
175
+ return VOICE_PROFILES[v] || VOICE_PROFILES.ash;
176
+ }
177
+ /** Initials, for anyone with no likeness. Two at most: a disc this size holds
178
+ * two letters and turns three into a smudge. */
179
+ function initialsOf(name) {
180
+ // A NAME IS NOT GUARANTEED. This draws a signed-in reader before their profile
181
+ // has arrived — `user` is a userinfo response, and a surface that renders on
182
+ // the credential renders before that response lands — so the one thing a face
183
+ // must never do is throw when it has nothing to spell. '?' is already what
184
+ // this returns for a name it cannot reduce; absent is that case, earlier.
185
+ return ((name ?? '')
186
+ .split(/[\s._-]+/)
187
+ .filter(Boolean)
188
+ .slice(0, 2)
189
+ .map((part) => part[0]?.toUpperCase() ?? '')
190
+ .join('') || '?');
191
+ }
192
+ const MEMOJIS = [
193
+ '/agents/leo.png',
194
+ '/agents/des.png',
195
+ '/agents/vi.png',
196
+ '/agents/creative.png',
197
+ '/agents/maya.png',
198
+ '/agents/nora.png',
199
+ '/agents/einstein.png',
200
+ ];
201
+ export function fallbackMemoji(name) {
202
+ if (!name)
203
+ return MEMOJIS[0];
204
+ let hash = 0;
205
+ for (let i = 0; i < name.length; i++) {
206
+ hash = (hash << 5) - hash + name.charCodeAt(i);
207
+ hash |= 0;
208
+ }
209
+ return MEMOJIS[Math.abs(hash) % MEMOJIS.length];
210
+ }
211
+ /**
212
+ * One character's disc — the likeness where there is one, initials where there
213
+ * is not.
214
+ *
215
+ * It was private to the roster and drew initials only, so an agent looked the
216
+ * same in the column, the sidebar and everywhere else: like nobody. One face,
217
+ * every caller, one size prop — because the sidebar's row and the roster's
218
+ * column want the same picture at two sizes, not two pictures.
219
+ */
220
+ export function Face({ src, emoji, name, title, size = 32, person = false, }) {
221
+ const role = person ? undefined : roleOf(name);
222
+ const cast = person ? undefined : role?.portrait || fallbackMemoji(name);
223
+ const [imgSrc, setImgSrc] = useState(src || (emoji ? undefined : cast));
224
+ const [failed, setFailed] = useState(false);
225
+ useEffect(() => {
226
+ setImgSrc(src || (emoji ? undefined : cast));
227
+ setFailed(false);
228
+ }, [src, emoji, cast]);
229
+ const handleError = () => {
230
+ if (failed)
231
+ return;
232
+ setFailed(true);
233
+ if (person) {
234
+ setImgSrc(undefined);
235
+ return;
236
+ }
237
+ const fallback = fallbackMemoji(name);
238
+ if (imgSrc !== fallback) {
239
+ setImgSrc(fallback);
240
+ }
241
+ else if (imgSrc !== MEMOJIS[0]) {
242
+ setImgSrc(MEMOJIS[0]);
243
+ }
244
+ };
245
+ const likeness = emoji ? undefined : imgSrc;
246
+ const label = title || name;
247
+ // A portrait's own ground is content, a hex the cast states; gui's colour types name only custom properties.
248
+ const ground = ((src ? undefined : role?.ground) || 'transparent');
249
+ const disc = (_jsx(XStack, { width: size, height: size, rounded: size / 2, overflow: "hidden", borderWidth: likeness || role?.ground ? 0 : 1, borderColor: "var(--border)", items: "center", justify: "center", bg: ground, "aria-label": title ? label : undefined, "aria-hidden": title ? undefined : true, children: emoji ? (_jsx(Text, { fontSize: Math.round(size * 0.55), lineHeight: size, children: emoji })) : likeness ? (_jsx(View, { render: _jsx("img", { src: likeness, alt: "", width: size, height: size, onError: handleError }), width: size, height: size, objectFit: "cover", display: "block" })) : (_jsx(Text, { fontSize: "$1", color: role?.ground ? 'var(--pure-white)' : '$soft', children: initialsOf(name) })) }));
250
+ return title ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { children: disc }), _jsx(TooltipContent, { children: _jsx(Text, { fontSize: "$2", children: title }) })] })) : disc;
251
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Switches one skill on or off for the caller's org and project, and answers
3
+ * with the whole resulting set.
4
+ *
5
+ * THE ANSWER IS THE SET, NOT AN ACKNOWLEDGEMENT, which is why the caller
6
+ * replaces its state with what comes back rather than flipping its own flag: an
7
+ * activation that was refused, clamped, or changed by somebody else in another
8
+ * tab is visible in the reply and invisible in an optimistic toggle.
9
+ */
10
+ export declare function activate(name: string, on: boolean): Promise<string[]>;
11
+ /** What a device sign-in needs from the reader. */
12
+ export interface Device {
13
+ /** The short code they type at {@link Device.verifyUrl}. */
14
+ userCode: string;
15
+ verifyUrl: string;
16
+ /** The id this flow is polled with, which the pane does not do — see below. */
17
+ flow: string;
18
+ }
19
+ /**
20
+ * Begins a device sign-in for one provider.
21
+ *
22
+ * THE PANE STARTS IT AND DOES NOT WATCH IT. Polling belongs where the result is
23
+ * used — the connector appears in the Directory's own list, which reads
24
+ * `/v1/integrations/connectors` — and a menu that closes the moment the reader
25
+ * leaves for the provider's page has nowhere to put a result anyway. Starting
26
+ * the flow, showing the code and opening the page is the whole of this control's
27
+ * job; `flow` is returned so a surface that does want to watch has the id.
28
+ */
29
+ export declare function startDevice(provider: string): Promise<Device>;
@@ -0,0 +1,64 @@
1
+ 'use client';
2
+ // The two writes the `+` menu makes.
3
+ //
4
+ // SEPARATE FROM `tools.ts` BECAUSE READING AND WRITING ARE DIFFERENT JOBS: the
5
+ // lists are cached for the document and shared by every menu that opens, and a
6
+ // write is a one-shot with an answer the caller acts on. Braiding them would
7
+ // put a cache invalidation inside a button press.
8
+ //
9
+ // BOTH SHAPES ARE THE PUBLISHED ONES, read off `/v1/openapi.json` rather than
10
+ // guessed: activation takes `{activate,deactivate}` arrays and answers with the
11
+ // resulting `enabled` set, and a device sign-in answers with the code to show
12
+ // and the page to show it at. Neither invents a field.
13
+ import { bearer, scope } from './lib/session.js';
14
+ import { api } from './lib/api.js';
15
+ /**
16
+ * A call that carries the session, or refuses before it is made.
17
+ *
18
+ * NO TOKEN IS A REFUSAL, NOT AN ANONYMOUS ATTEMPT. Both of these routes are
19
+ * org-scoped and answer "a validated principal is required"; sending anyway
20
+ * spends a round trip to be told what we already knew, and returns a 403 the
21
+ * caller then has to translate back into "sign in".
22
+ */
23
+ async function ask(path, body) {
24
+ const token = bearer();
25
+ if (!token)
26
+ throw new Error('Sign in to change this.');
27
+ const r = await fetch(`${api()}${path}`, {
28
+ method: path.endsWith('/activation') ? 'PUT' : 'POST',
29
+ headers: { 'Content-Type': 'application/json', ...scope() },
30
+ body: JSON.stringify(body),
31
+ });
32
+ if (!r.ok)
33
+ throw Object.assign(new Error(`${r.status}`), { status: r.status });
34
+ return (await r.json());
35
+ }
36
+ /**
37
+ * Switches one skill on or off for the caller's org and project, and answers
38
+ * with the whole resulting set.
39
+ *
40
+ * THE ANSWER IS THE SET, NOT AN ACKNOWLEDGEMENT, which is why the caller
41
+ * replaces its state with what comes back rather than flipping its own flag: an
42
+ * activation that was refused, clamped, or changed by somebody else in another
43
+ * tab is visible in the reply and invisible in an optimistic toggle.
44
+ */
45
+ export async function activate(name, on) {
46
+ const done = await ask('/v1/tools/activation', on ? { activate: [name] } : { deactivate: [name] });
47
+ return done.enabled ?? [];
48
+ }
49
+ /**
50
+ * Begins a device sign-in for one provider.
51
+ *
52
+ * THE PANE STARTS IT AND DOES NOT WATCH IT. Polling belongs where the result is
53
+ * used — the connector appears in the Directory's own list, which reads
54
+ * `/v1/integrations/connectors` — and a menu that closes the moment the reader
55
+ * leaves for the provider's page has nowhere to put a result anyway. Starting
56
+ * the flow, showing the code and opening the page is the whole of this control's
57
+ * job; `flow` is returned so a surface that does want to watch has the id.
58
+ */
59
+ export async function startDevice(provider) {
60
+ const begun = await ask(`/v1/integrations/connectors/${encodeURIComponent(provider)}/device`, {});
61
+ if (!begun.userCode || !begun.verifyUrl)
62
+ throw new Error('This service did not start a sign-in.');
63
+ return { userCode: begun.userCode, verifyUrl: begun.verifyUrl, flow: begun.flow ?? '' };
64
+ }