@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.
- package/LICENSE-APACHE +205 -0
- package/LICENSE-MIT +21 -0
- package/README.md +23 -0
- package/dist/Account.d.ts +8 -0
- package/dist/Account.js +58 -0
- package/dist/Beluga.d.ts +12 -0
- package/dist/Beluga.js +152 -0
- package/dist/Billing.d.ts +1 -0
- package/dist/Billing.js +31 -0
- package/dist/Board.d.ts +9 -0
- package/dist/Board.js +447 -0
- package/dist/Boxes.d.ts +1 -0
- package/dist/Boxes.js +173 -0
- package/dist/CalScheduler.d.ts +15 -0
- package/dist/CalScheduler.js +196 -0
- package/dist/Chat.d.ts +35 -0
- package/dist/Chat.js +1049 -0
- package/dist/Column.d.ts +15 -0
- package/dist/Column.js +88 -0
- package/dist/Contacts.d.ts +1 -0
- package/dist/Contacts.js +301 -0
- package/dist/Directory.d.ts +24 -0
- package/dist/Directory.js +356 -0
- package/dist/Drive.d.ts +2 -0
- package/dist/Drive.js +466 -0
- package/dist/Find.d.ts +7 -0
- package/dist/Find.js +82 -0
- package/dist/Guide.d.ts +6 -0
- package/dist/Guide.js +272 -0
- package/dist/Home.d.ts +39 -0
- package/dist/Home.js +306 -0
- package/dist/Inbox.d.ts +9 -0
- package/dist/Inbox.js +193 -0
- package/dist/Market.d.ts +3 -0
- package/dist/Market.js +149 -0
- package/dist/Me.d.ts +27 -0
- package/dist/Me.js +251 -0
- package/dist/Meet.d.ts +9 -0
- package/dist/Meet.js +370 -0
- package/dist/ModeMark.d.ts +8 -0
- package/dist/ModeMark.js +18 -0
- package/dist/OrgSwitcher.d.ts +31 -0
- package/dist/OrgSwitcher.js +120 -0
- package/dist/Orgs.d.ts +10 -0
- package/dist/Orgs.js +582 -0
- package/dist/Profile.d.ts +4 -0
- package/dist/Profile.js +67 -0
- package/dist/Prose.d.ts +5 -0
- package/dist/Prose.js +111 -0
- package/dist/ProviderMark.d.ts +17 -0
- package/dist/ProviderMark.js +277 -0
- package/dist/Recents.d.ts +10 -0
- package/dist/Recents.js +91 -0
- package/dist/RightPane.d.ts +13 -0
- package/dist/RightPane.js +426 -0
- package/dist/Room.d.ts +8 -0
- package/dist/Room.js +50 -0
- package/dist/Scene.d.ts +12 -0
- package/dist/Scene.js +105 -0
- package/dist/Settings.d.ts +46 -0
- package/dist/Settings.js +321 -0
- package/dist/Setup.d.ts +1 -0
- package/dist/Setup.js +36 -0
- package/dist/Share.d.ts +3 -0
- package/dist/Share.js +130 -0
- package/dist/Shared.d.ts +1 -0
- package/dist/Shared.js +113 -0
- package/dist/Shell.d.ts +63 -0
- package/dist/Shell.js +1225 -0
- package/dist/Start.d.ts +1 -0
- package/dist/Start.js +96 -0
- package/dist/Support.d.ts +14 -0
- package/dist/Support.js +122 -0
- package/dist/Talk.d.ts +4 -0
- package/dist/Talk.js +107 -0
- package/dist/Upgrade.d.ts +11 -0
- package/dist/Upgrade.js +75 -0
- package/dist/Usage.d.ts +1 -0
- package/dist/Usage.js +21 -0
- package/dist/Vibe.d.ts +2 -0
- package/dist/Vibe.js +188 -0
- package/dist/Work.d.ts +12 -0
- package/dist/Work.js +256 -0
- package/dist/bots/Bots.d.ts +2 -0
- package/dist/bots/Bots.js +456 -0
- package/dist/bots/Lights.d.ts +8 -0
- package/dist/bots/Lights.js +13 -0
- package/dist/bots/api.d.ts +120 -0
- package/dist/bots/api.js +75 -0
- package/dist/bots/face.d.ts +5 -0
- package/dist/bots/face.js +13 -0
- package/dist/bots/ink.d.ts +7 -0
- package/dist/bots/ink.js +29 -0
- package/dist/bots/kit.d.ts +113 -0
- package/dist/bots/kit.js +43 -0
- package/dist/bots/runtime.d.ts +4 -0
- package/dist/bots/runtime.js +326 -0
- package/dist/card.d.ts +52 -0
- package/dist/card.js +52 -0
- package/dist/cast.d.ts +143 -0
- package/dist/cast.js +251 -0
- package/dist/connect.d.ts +29 -0
- package/dist/connect.js +64 -0
- package/dist/contacts-store.d.ts +111 -0
- package/dist/contacts-store.js +276 -0
- package/dist/conversations.d.ts +54 -0
- package/dist/conversations.js +98 -0
- package/dist/copy.d.ts +23 -0
- package/dist/copy.js +39 -0
- package/dist/crew.d.ts +36 -0
- package/dist/crew.js +304 -0
- package/dist/dev.d.ts +3 -0
- package/dist/dev.js +102 -0
- package/dist/enso.d.ts +70 -0
- package/dist/enso.js +112 -0
- package/dist/failure.d.ts +57 -0
- package/dist/failure.js +143 -0
- package/dist/ground.d.ts +32 -0
- package/dist/ground.js +32 -0
- package/dist/host.d.ts +76 -0
- package/dist/host.js +76 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +12 -0
- package/dist/kept.d.ts +5 -0
- package/dist/kept.js +27 -0
- package/dist/lib/account.d.ts +49 -0
- package/dist/lib/account.js +229 -0
- package/dist/lib/ai.d.ts +46 -0
- package/dist/lib/ai.js +140 -0
- package/dist/lib/api.d.ts +43 -0
- package/dist/lib/api.js +62 -0
- package/dist/lib/bare.d.ts +9 -0
- package/dist/lib/bare.js +7 -0
- package/dist/lib/coding.d.ts +105 -0
- package/dist/lib/coding.js +87 -0
- package/dist/lib/destination.d.ts +102 -0
- package/dist/lib/destination.js +260 -0
- package/dist/lib/durable.d.ts +66 -0
- package/dist/lib/durable.js +107 -0
- package/dist/lib/first.d.ts +11 -0
- package/dist/lib/first.js +23 -0
- package/dist/lib/home.d.ts +61 -0
- package/dist/lib/home.js +54 -0
- package/dist/lib/host.d.ts +104 -0
- package/dist/lib/host.js +132 -0
- package/dist/lib/hostname.d.ts +1 -0
- package/dist/lib/hostname.js +14 -0
- package/dist/lib/hydrated.d.ts +1 -0
- package/dist/lib/hydrated.js +20 -0
- package/dist/lib/integrations.d.ts +21 -0
- package/dist/lib/integrations.js +1 -0
- package/dist/lib/intent.d.ts +6 -0
- package/dist/lib/intent.js +71 -0
- package/dist/lib/invite.d.ts +155 -0
- package/dist/lib/invite.js +245 -0
- package/dist/lib/limits.d.ts +55 -0
- package/dist/lib/limits.js +119 -0
- package/dist/lib/list.d.ts +9 -0
- package/dist/lib/list.js +11 -0
- package/dist/lib/mark.d.ts +13 -0
- package/dist/lib/mark.js +85 -0
- package/dist/lib/mix.d.ts +22 -0
- package/dist/lib/mix.js +22 -0
- package/dist/lib/models.d.ts +27 -0
- package/dist/lib/models.js +106 -0
- package/dist/lib/org.d.ts +109 -0
- package/dist/lib/org.js +140 -0
- package/dist/lib/path.d.ts +9 -0
- package/dist/lib/path.js +29 -0
- package/dist/lib/plans.d.ts +193 -0
- package/dist/lib/plans.js +310 -0
- package/dist/lib/prose.d.ts +13 -0
- package/dist/lib/prose.js +62 -0
- package/dist/lib/reach.d.ts +17 -0
- package/dist/lib/reach.js +70 -0
- package/dist/lib/rooms.json +23 -0
- package/dist/lib/served.d.ts +20 -0
- package/dist/lib/served.js +92 -0
- package/dist/lib/session.d.ts +211 -0
- package/dist/lib/session.js +493 -0
- package/dist/lib/share.d.ts +131 -0
- package/dist/lib/share.js +216 -0
- package/dist/lib/signout.d.ts +14 -0
- package/dist/lib/signout.js +40 -0
- package/dist/lib/tags.d.ts +27 -0
- package/dist/lib/tags.js +46 -0
- package/dist/lib/tap.d.ts +17 -0
- package/dist/lib/tap.js +9 -0
- package/dist/lib/tier.d.ts +115 -0
- package/dist/lib/tier.js +207 -0
- package/dist/lib/tiers.d.ts +9 -0
- package/dist/lib/tiers.js +141 -0
- package/dist/lib/todo.d.ts +125 -0
- package/dist/lib/todo.js +379 -0
- package/dist/look.d.ts +1 -0
- package/dist/look.js +21 -0
- package/dist/meters.d.ts +8 -0
- package/dist/meters.js +31 -0
- package/dist/model.d.ts +4 -0
- package/dist/model.js +44 -0
- package/dist/network.d.ts +64 -0
- package/dist/network.js +216 -0
- package/dist/open.d.ts +81 -0
- package/dist/open.js +149 -0
- package/dist/pane.d.ts +92 -0
- package/dist/pane.js +231 -0
- package/dist/progress.d.ts +46 -0
- package/dist/progress.js +51 -0
- package/dist/recent.d.ts +67 -0
- package/dist/recent.js +161 -0
- package/dist/rooms.d.ts +131 -0
- package/dist/rooms.js +195 -0
- package/dist/roster.d.ts +28 -0
- package/dist/roster.js +70 -0
- package/dist/span.d.ts +14 -0
- package/dist/span.js +50 -0
- package/dist/speech.d.ts +44 -0
- package/dist/speech.js +229 -0
- package/dist/stars.d.ts +16 -0
- package/dist/stars.js +75 -0
- package/dist/tabs.d.ts +26 -0
- package/dist/tabs.js +25 -0
- package/dist/team.d.ts +11 -0
- package/dist/team.gen.d.ts +10 -0
- package/dist/team.gen.js +11 -0
- package/dist/team.js +30 -0
- package/dist/tools.d.ts +35 -0
- package/dist/tools.js +143 -0
- package/dist/tours.d.ts +24 -0
- package/dist/tours.js +311 -0
- package/dist/waiting.d.ts +15 -0
- package/dist/waiting.js +17 -0
- package/dist/where.d.ts +54 -0
- package/dist/where.js +14 -0
- 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>;
|
package/dist/connect.js
ADDED
|
@@ -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
|
+
}
|