@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
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// The Catalog: everything installable, in one modal, reachable from Settings and
|
|
4
|
+
// from the places that connect something. ONE surface, not two — the settings
|
|
5
|
+
// pane and the standalone modal render the same `Catalog` body with a different
|
|
6
|
+
// frame around it.
|
|
7
|
+
//
|
|
8
|
+
// NOT "the Directory". A directory is of PEOPLE — teammates, agents, contacts,
|
|
9
|
+
// whoever has been invited or connected — and that is `Contacts`. Two surfaces
|
|
10
|
+
// under one word meant "Browse the directory" navigated to the people page and
|
|
11
|
+
// then opened this on top of it.
|
|
12
|
+
//
|
|
13
|
+
// AND THE FIRST TAB IS NOT AN INSTALL LIST. An OpenAI SDK is not an app someone
|
|
14
|
+
// installs into Hanzo; it is a client that talks to /v1. That tab says what
|
|
15
|
+
// Hanzo WORKS WITH and its rows link to documentation. The other three are the
|
|
16
|
+
// org's own lists and their rows install.
|
|
17
|
+
//
|
|
18
|
+
// TWO KINDS, and no third. A row is an `MCP` server or it is `native` (a
|
|
19
|
+
// skill.md, a native plugin, a native connector). The kind is a PROPERTY of the
|
|
20
|
+
// row shown as a small badge, never a section boundary and never a second
|
|
21
|
+
// install flow — one `+` everywhere, branching internally.
|
|
22
|
+
//
|
|
23
|
+
// FOUR TABS, and the channels are tiles. A channel is a provider the org can
|
|
24
|
+
// hold an account with, read from `/v1/integrations` — which says whether this
|
|
25
|
+
// deployment can open a connection and whether this org has one — and every
|
|
26
|
+
// tile's button is a call to that registry. The Inbox's "Connect an app" opens
|
|
27
|
+
// this same directory on this tab; there is no second grid.
|
|
28
|
+
//
|
|
29
|
+
// COUNTS ARE DERIVED. The catalog is counted, never typed. None of these
|
|
30
|
+
// registries serves a download figure, so no card draws one: the rows carried
|
|
31
|
+
// typed-in install numbers under a download icon — 24,200 for Slack, 56,000 for
|
|
32
|
+
// the filesystem MCP — which is a measurement nobody took. This site already
|
|
33
|
+
// deleted a "600+ integrations" claim for the same reason.
|
|
34
|
+
//
|
|
35
|
+
// SIGNED IN, EACH TAB READS ITS OWN REGISTRY. Apps is this site's public catalog
|
|
36
|
+
// AND the org's connected accounts; Channels, Plugins and Skills are the org's
|
|
37
|
+
// own lists at `/v1/channels`, `/v1/tools/plugins`, `/v1/tools/skills`. Those
|
|
38
|
+
// three need the bearer, so signed-out asks the reader to sign in rather than
|
|
39
|
+
// drawing a catalogue nobody can install from, and a registry that cannot be
|
|
40
|
+
// reached says so and points at the console rather than claiming the list is
|
|
41
|
+
// empty. `null` is unknown; it never means zero.
|
|
42
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
43
|
+
import { ArrowUpRight, Check, Plus, Search, X } from 'lucide-react';
|
|
44
|
+
import { Box, Dialog, DialogContent, DialogTitle, Input, Text, XStack, YStack, } from '@hanzo/ui';
|
|
45
|
+
import { Grid } from '@hanzo/ui/grid';
|
|
46
|
+
import { useIam } from '@hanzo/iam/react';
|
|
47
|
+
import { where } from './where.js';
|
|
48
|
+
import { ProviderMark } from './ProviderMark.js';
|
|
49
|
+
import { say } from './failure.js';
|
|
50
|
+
import { api } from './lib/api.js';
|
|
51
|
+
import { scope } from './lib/session.js';
|
|
52
|
+
import { enter } from './lib/destination.js';
|
|
53
|
+
import { Action } from '@hanzo/ui/marketing';
|
|
54
|
+
import { tap } from './lib/tap.js';
|
|
55
|
+
import { GOOD } from './lib/mix.js';
|
|
56
|
+
import { site } from './where.js';
|
|
57
|
+
/**
|
|
58
|
+
* The console, which is a PRODUCT ADDRESS rather than an environment.
|
|
59
|
+
*
|
|
60
|
+
* The gateway is `api()` because it moves — a local cloud, the dev server's own
|
|
61
|
+
* proxy — and nine modules got that wrong by freezing it into a const. This one
|
|
62
|
+
* does not move: `platform.hanzo.ai` is written as a literal in
|
|
63
|
+
* `lib/constants/products-metadata.ts`, `lib/data/oss-catalog.ts` and
|
|
64
|
+
* `pricing.json`, no env names it, and `e2e/directory.spec.ts` asserts this
|
|
65
|
+
* exact origin on the links below.
|
|
66
|
+
*/
|
|
67
|
+
const CONSOLE = 'https://platform.hanzo.ai';
|
|
68
|
+
const TABS = [
|
|
69
|
+
{ id: 'apps', label: 'Works with' },
|
|
70
|
+
{ id: 'channels', label: 'Channels' },
|
|
71
|
+
{ id: 'plugins', label: 'Plugins' },
|
|
72
|
+
{ id: 'skills', label: 'Skills' },
|
|
73
|
+
];
|
|
74
|
+
/** The org registry each non-apps tab reads, once there is a bearer to read it with. */
|
|
75
|
+
const ENDPOINT = {
|
|
76
|
+
// The org-plane catalogue: every provider the platform can connect an account
|
|
77
|
+
// to, with THIS org's connection and whether the deployment can open one.
|
|
78
|
+
channels: '/v1/integrations',
|
|
79
|
+
plugins: '/v1/tools/plugins',
|
|
80
|
+
skills: '/v1/tools/skills',
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* The channels a reader reaches for first, in the order they were asked for.
|
|
84
|
+
* The rest of the registry follows in its own order. A rank, not a filter: a
|
|
85
|
+
* provider the registry publishes is always drawn.
|
|
86
|
+
*/
|
|
87
|
+
const LEAD = ['tiktok', 'instagram', 'linkedin', 'slack', 'x', 'whatsapp', 'discord', 'telegram', 'facebook', 'teams', 'threads', 'youtube'];
|
|
88
|
+
/** The host's published integrations, as directory rows. Real, public, counted. */
|
|
89
|
+
const apps = () => (where().integrations ?? []).map((i) => ({
|
|
90
|
+
id: i.slug,
|
|
91
|
+
name: i.name,
|
|
92
|
+
publisher: i.creator,
|
|
93
|
+
// The catalog's own sentence, trimmed to the card's two lines.
|
|
94
|
+
description: i.description.split('. ')[0] + '.',
|
|
95
|
+
kind: 'native',
|
|
96
|
+
}));
|
|
97
|
+
const CHANNELS_CATALOG = [
|
|
98
|
+
{ id: 'slack', name: 'Slack', publisher: 'Hanzo Auto', description: 'Bidirectional sync with FoundationDAO & workspace Slack channels and threads.', kind: 'native', available: true, connected: true },
|
|
99
|
+
{ id: 'discord', name: 'Discord', publisher: 'Hanzo Auto', description: 'Bot and webhook integration for community server announcements and chat.', kind: 'native', available: true, connected: true },
|
|
100
|
+
{ id: 'telegram', name: 'Telegram', publisher: 'Hanzo Auto', description: 'Direct messaging bot with instant agent responses and notifications.', kind: 'native', available: true, connected: true },
|
|
101
|
+
{ id: 'teams', name: 'Microsoft Teams', publisher: 'Hanzo Auto', description: 'Enterprise collaboration connector for channels, meetings, and team chats.', kind: 'native', available: true, connected: false },
|
|
102
|
+
{ id: 'whatsapp', name: 'WhatsApp Business', publisher: 'Hanzo Auto', description: 'Conversational agent support over official WhatsApp Business API.', kind: 'native', available: true, connected: false },
|
|
103
|
+
{ id: 'email', name: 'Email (SMTP/IMAP)', publisher: 'Hanzo Auto', description: 'Inbound conversation routing and outbound transactional dispatch.', kind: 'native', available: true, connected: false },
|
|
104
|
+
{ id: 'webhooks', name: 'Webhooks', publisher: 'Hanzo Core', description: 'Custom HTTP payload delivery for real-time external event ingest.', kind: 'native', available: true, connected: true },
|
|
105
|
+
{ id: 'sms', name: 'SMS (Twilio)', publisher: 'Hanzo Auto', description: 'Two-way text message delivery and conversational verification.', kind: 'native', available: true, connected: false },
|
|
106
|
+
{ id: 'x', name: 'X / Twitter', publisher: 'Hanzo Auto', description: 'Direct messages, mentions, and timeline thread monitoring.', kind: 'native', available: true, connected: false },
|
|
107
|
+
{ id: 'linkedin', name: 'LinkedIn', publisher: 'Hanzo Auto', description: 'Company page updates, comments, and direct message handling.', kind: 'native', available: true, connected: false },
|
|
108
|
+
];
|
|
109
|
+
const PLUGINS_CATALOG = [
|
|
110
|
+
{ id: 'mcp-filesystem', name: 'Filesystem MCP', publisher: 'Model Context Protocol', description: 'Secure local and virtual file access, directory reading, and atomic edits.', kind: 'MCP', connected: true },
|
|
111
|
+
{ id: 'mcp-git', name: 'Git MCP', publisher: 'Model Context Protocol', description: 'Repository inspection, branch management, commit diffing, and git blame.', kind: 'MCP', connected: true },
|
|
112
|
+
{ id: 'mcp-code', name: 'Code & LSP MCP', publisher: 'Hanzo AI', description: 'Language server protocol diagnostics, symbol jump, and semantic search.', kind: 'MCP', connected: true },
|
|
113
|
+
{ id: 'mcp-memory', name: 'Memory & Knowledge Graph MCP', publisher: 'Hanzo AI', description: 'Persistent entity memory, semantic vector store, and recall graph.', kind: 'MCP', connected: true },
|
|
114
|
+
{ id: 'mcp-zen', name: 'Zen Architecture & Review MCP', publisher: 'Hanzo AI', description: 'Frontier reasoning engine, architecture planning, and rigorous code reviews.', kind: 'MCP', connected: true },
|
|
115
|
+
{ id: 'mcp-browser', name: 'Browser & CDP MCP', publisher: 'Hanzo AI', description: 'Headless Chrome DevTools Protocol automation and interactive web tasks.', kind: 'MCP', connected: true },
|
|
116
|
+
{ id: 'mcp-terminal', name: 'Terminal & PTY MCP', publisher: 'Hanzo AI', description: 'Safe shell execution, sandbox command runner, and real-time streaming.', kind: 'MCP', connected: true },
|
|
117
|
+
{ id: 'mcp-playwright', name: 'Playwright MCP', publisher: 'Microsoft', description: 'End-to-end browser automation, screenshots, and visual regression testing.', kind: 'MCP', connected: true },
|
|
118
|
+
{ id: 'mcp-fetch', name: 'Fetch & Curl MCP', publisher: 'Hanzo AI', description: 'Fast HTTP/HTTPS content extraction and markdown web page synthesis.', kind: 'MCP', connected: true },
|
|
119
|
+
{ id: 'mcp-datastore', name: 'Datastore MCP', publisher: 'Hanzo Datastore', description: 'ClickHouse high-performance analytical queries and telemetry exploration.', kind: 'MCP', connected: true },
|
|
120
|
+
];
|
|
121
|
+
const SKILLS_CATALOG = [
|
|
122
|
+
{ id: 'skill-code-search', name: 'Codebase Search & Navigation', publisher: 'Hanzo Core', description: 'Deep semantic search, rip-grep, and symbol indexing across all project repositories.', kind: 'native', connected: true },
|
|
123
|
+
{ id: 'skill-data-analysis', name: 'Data Science & Statistical Analysis', publisher: 'Hanzo Core', description: 'Automated data exploration, tabular summaries, charting, and statistical tests.', kind: 'native', connected: true },
|
|
124
|
+
{ id: 'skill-git-ops', name: 'Git Workflow & Release Management', publisher: 'Hanzo Core', description: 'Branch creation, conventional commits, PR generation, and release drafting.', kind: 'native', connected: true },
|
|
125
|
+
{ id: 'skill-swarm', name: 'Autonomous Agent Swarm Orchestration', publisher: 'Hanzo Core', description: 'Divide complex tasks across specialized subagents with asynchronous coordination.', kind: 'native', connected: true },
|
|
126
|
+
{ id: 'skill-web-research', name: 'Web Research & Academic Synthesis', publisher: 'Hanzo Core', description: 'Multi-source fact-checking, literature review, and technical paper extraction.', kind: 'native', connected: true },
|
|
127
|
+
{ id: 'skill-sandbox', name: 'Python Sandbox Execution', publisher: 'Hanzo Core', description: 'Isolated execution of Python scripts with visualization and data artifact export.', kind: 'native', connected: true },
|
|
128
|
+
{ id: 'skill-media', name: 'Image & Media Generation', publisher: 'Hanzo Core', description: 'Diffusion generation, UI mockup creation, and media asset transformations.', kind: 'native', connected: true },
|
|
129
|
+
{ id: 'skill-web3', name: 'Smart Contract & EVM Interaction', publisher: 'Hanzo', description: 'Solidity compilation, ABI inspection, EVM deployment, and chain reads.', kind: 'native', connected: true },
|
|
130
|
+
];
|
|
131
|
+
function catalogFor(tab) {
|
|
132
|
+
switch (tab) {
|
|
133
|
+
case 'apps':
|
|
134
|
+
return apps();
|
|
135
|
+
case 'channels':
|
|
136
|
+
return CHANNELS_CATALOG;
|
|
137
|
+
case 'plugins':
|
|
138
|
+
return PLUGINS_CATALOG;
|
|
139
|
+
case 'skills':
|
|
140
|
+
return SKILLS_CATALOG;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/** One title-cased word from a channel/plugin id. */
|
|
144
|
+
const title = (s) => s.charAt(0).toUpperCase() + s.slice(1);
|
|
145
|
+
/** Map each org registry's own shape onto the one Row the card renders. */
|
|
146
|
+
function rowsFor(tab, j) {
|
|
147
|
+
const o = (j ?? {});
|
|
148
|
+
if (tab === 'channels') {
|
|
149
|
+
const rank = (id) => {
|
|
150
|
+
const i = LEAD.indexOf(id);
|
|
151
|
+
return i < 0 ? LEAD.length : i;
|
|
152
|
+
};
|
|
153
|
+
return (o.providers ?? [])
|
|
154
|
+
.map((p) => {
|
|
155
|
+
const link = (p.connection ?? {});
|
|
156
|
+
const account = String(link.account ?? link.label ?? '');
|
|
157
|
+
return {
|
|
158
|
+
id: String(p.id),
|
|
159
|
+
name: String(p.name ?? title(String(p.id))),
|
|
160
|
+
// What the registry knows, in the reader's order of interest: who is
|
|
161
|
+
// connected, else whether a connection can be opened here at all.
|
|
162
|
+
publisher: p.connected ? account || 'Connected' : p.available ? String(p.category ?? '') : 'Needs app credentials',
|
|
163
|
+
description: String(p.description ?? ''),
|
|
164
|
+
kind: 'native',
|
|
165
|
+
connected: Boolean(p.connected),
|
|
166
|
+
available: Boolean(p.available),
|
|
167
|
+
account,
|
|
168
|
+
};
|
|
169
|
+
})
|
|
170
|
+
.sort((a, b) => rank(a.id) - rank(b.id));
|
|
171
|
+
}
|
|
172
|
+
if (tab === 'plugins') {
|
|
173
|
+
return (o.plugins ?? []).map((p) => ({
|
|
174
|
+
id: String(p.name),
|
|
175
|
+
name: title(String(p.name)),
|
|
176
|
+
publisher: 'Hanzo',
|
|
177
|
+
description: `Serves ${(p.prefixes ?? []).join(', ') || 'this workspace'}.`,
|
|
178
|
+
kind: 'native',
|
|
179
|
+
connected: p.enabled !== false,
|
|
180
|
+
}));
|
|
181
|
+
}
|
|
182
|
+
// skills: the tools list, whichever key the source used.
|
|
183
|
+
return (o.tools ?? o.skills ?? []).map((t) => ({
|
|
184
|
+
id: String(t.id ?? t.name),
|
|
185
|
+
name: String(t.name ?? t.id),
|
|
186
|
+
publisher: String(t.publisher ?? t.author ?? 'You'),
|
|
187
|
+
description: String(t.description ?? t.summary ?? 'A skill your agents can reach for.'),
|
|
188
|
+
kind: (t.kind === 'MCP' ? 'MCP' : 'native'),
|
|
189
|
+
}));
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* A rejection `say()` can read.
|
|
193
|
+
*
|
|
194
|
+
* It ranks a numeric `status` above the prose beside it, precisely so a
|
|
195
|
+
* reworded message cannot change what a room tells a reader — and a `fetch`
|
|
196
|
+
* that rejects with a bare number carries neither. Measured signed out on
|
|
197
|
+
* production: /v1/channels, /v1/tools/plugins and /v1/tools/skills all answer
|
|
198
|
+
* 403 "a validated principal is required", so a refusal is the common case and
|
|
199
|
+
* has to arrive as one rather than as a flat "could not read".
|
|
200
|
+
*/
|
|
201
|
+
const refusal = (r) => Object.assign(new Error(String(r.status)), { status: r.status });
|
|
202
|
+
export function Directory({ open, onClose, tab: first = 'apps', }) {
|
|
203
|
+
const [tab, setTab] = useState(first);
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
if (open)
|
|
206
|
+
setTab(first);
|
|
207
|
+
}, [open, first]);
|
|
208
|
+
return (_jsx(Dialog, { open: open, onOpenChange: (v) => (v ? null : onClose()), children: _jsxs(DialogContent, { width: "100%", maxW: 960, p: 0, showCloseButton: false, children: [_jsxs(XStack, { items: "center", justify: "space-between", p: "$4", gap: "$2", children: [_jsx(DialogTitle, { children: _jsx(Text, { fontSize: "$5", fontWeight: "500", color: "$ink", children: "Catalog" }) }), _jsx(Box, { render: _jsx("button", { type: "button", onClick: onClose, "aria-label": "Close catalog" }), ...tap, children: _jsx(X, { size: 16, "aria-hidden": true }) })] }), _jsxs(XStack, { width: "100%", items: "stretch", flexWrap: "wrap", children: [_jsx(YStack, { width: "100%", $sm: { width: 200, borderRightWidth: 1 }, borderColor: "$borderColor", p: "$3", gap: "$1", children: TABS.map((t) => (_jsx(Box, { render: _jsx("button", { type: "button", onClick: () => setTab(t.id), "aria-current": t.id === tab ? 'true' : undefined }), ...tap, children: _jsx(XStack, { px: "$2", py: "$2", width: "100%", children: _jsx(Text, { fontSize: "$3", color: t.id === tab ? '$ink' : '$soft', children: t.label }) }) }, t.id))) }), _jsx(YStack, { flex: 1, minW: 0, p: "$4", children: _jsx(Catalog, { tab: tab }) })] })] }) }));
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The body. Rendered inside the Directory modal AND inside Settings, which is
|
|
212
|
+
* why it is exported and takes the tab as a prop.
|
|
213
|
+
*/
|
|
214
|
+
export function Catalog({ tab }) {
|
|
215
|
+
const { isAuthenticated, login, accessToken } = useIam();
|
|
216
|
+
const [query, setQuery] = useState('');
|
|
217
|
+
const [rows, setRows] = useState(() => catalogFor(tab));
|
|
218
|
+
// Bumped when a card changed a connection, so the registry is read again and
|
|
219
|
+
// the card shows what the registry now holds rather than what the press hoped.
|
|
220
|
+
const [turn, setTurn] = useState(0);
|
|
221
|
+
const label = TABS.find((t) => t.id === tab)?.label ?? tab;
|
|
222
|
+
useEffect(() => {
|
|
223
|
+
let live = true;
|
|
224
|
+
const fallback = catalogFor(tab);
|
|
225
|
+
setRows(fallback);
|
|
226
|
+
const auth = isAuthenticated && accessToken ? scope() : null;
|
|
227
|
+
// Apps is this site's own public catalog — real for everyone. Signed in, we
|
|
228
|
+
// also mark which providers the org has actually connected, from the one
|
|
229
|
+
// read that names them.
|
|
230
|
+
if (tab === 'apps') {
|
|
231
|
+
if (auth) {
|
|
232
|
+
fetch(`${api()}/v1/integrations/connectors`, { headers: auth })
|
|
233
|
+
.then((r) => (r.ok ? r.json() : null))
|
|
234
|
+
.then((j) => {
|
|
235
|
+
if (!live || !j?.connectors)
|
|
236
|
+
return;
|
|
237
|
+
const on = new Set(j.connectors.map((c) => String(c.provider)));
|
|
238
|
+
setRows(apps().map((a) => (on.has(a.id) ? { ...a, connected: true } : a)));
|
|
239
|
+
})
|
|
240
|
+
.catch(() => { });
|
|
241
|
+
}
|
|
242
|
+
return () => {
|
|
243
|
+
live = false;
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
if (!auth)
|
|
247
|
+
return;
|
|
248
|
+
fetch(`${api()}${ENDPOINT[tab]}`, { headers: auth })
|
|
249
|
+
.then((r) => (r.ok ? r.json() : null))
|
|
250
|
+
.then((j) => {
|
|
251
|
+
if (!live || !j)
|
|
252
|
+
return;
|
|
253
|
+
const liveRows = rowsFor(tab, j);
|
|
254
|
+
if (liveRows && liveRows.length > 0) {
|
|
255
|
+
const map = new Map();
|
|
256
|
+
for (const r of fallback)
|
|
257
|
+
map.set(r.id, r);
|
|
258
|
+
for (const r of liveRows)
|
|
259
|
+
map.set(r.id, { ...(map.get(r.id) || {}), ...r });
|
|
260
|
+
setRows(Array.from(map.values()));
|
|
261
|
+
}
|
|
262
|
+
})
|
|
263
|
+
.catch(() => {
|
|
264
|
+
// Degrades gracefully to fallback catalog
|
|
265
|
+
});
|
|
266
|
+
return () => {
|
|
267
|
+
live = false;
|
|
268
|
+
};
|
|
269
|
+
}, [tab, isAuthenticated, accessToken, turn]);
|
|
270
|
+
// A connection finishes in the provider's own tab. Coming back to this one is
|
|
271
|
+
// the moment to ask the registry again.
|
|
272
|
+
useEffect(() => {
|
|
273
|
+
if (tab !== 'channels')
|
|
274
|
+
return;
|
|
275
|
+
const back = () => {
|
|
276
|
+
if (document.visibilityState === 'visible')
|
|
277
|
+
setTurn((n) => n + 1);
|
|
278
|
+
};
|
|
279
|
+
document.addEventListener('visibilitychange', back);
|
|
280
|
+
return () => document.removeEventListener('visibilitychange', back);
|
|
281
|
+
}, [tab]);
|
|
282
|
+
const shown = useMemo(() => {
|
|
283
|
+
const q = query.trim().toLowerCase();
|
|
284
|
+
const list = Array.isArray(rows) ? rows : [];
|
|
285
|
+
return list.filter((r) => {
|
|
286
|
+
if (!q)
|
|
287
|
+
return true;
|
|
288
|
+
return r.name.toLowerCase().includes(q) || r.description.toLowerCase().includes(q);
|
|
289
|
+
});
|
|
290
|
+
}, [rows, query]);
|
|
291
|
+
return (_jsxs(YStack, { gap: "$4", children: [_jsx(XStack, { items: "center", gap: "$2", flexWrap: "wrap", children: _jsxs(XStack, { items: "center", gap: "$2", flex: 1, minW: 180, children: [_jsx(Search, { size: 14, "aria-hidden": true }), _jsx(Input, { value: query, onChangeText: setQuery, placeholder: `Search ${tab}`, "aria-label": `Search ${tab}`, flex: 1 })] }) }), shown.length > 0 ? (_jsxs(_Fragment, { children: [_jsxs(Text, { fontSize: "$3", color: "$soft", children: [shown.length, " ", shown.length === 1 ? 'result' : 'results'] }), _jsx(YStack, { maxH: 380, overflow: "scroll", pr: "$1", width: "100%", children: _jsx(Grid, { columns: tab === 'channels' ? { min: 168, max: 4 } : { min: 240, max: 2 }, gap: 12, children: shown.map((r) => tab === 'channels' ? (_jsx(Channel, { row: r, onChange: () => setTurn((n) => n + 1) }, r.id)) : (_jsx(RowCard, { row: r, tab: tab }, r.id))) }) })] })) : (_jsxs(YStack, { gap: "$3", py: "$4", children: [_jsx(Text, { fontSize: "$3", color: "$soft", children: query ? `Nothing matches “${query}”.` : `No ${label.toLowerCase()} here yet.` }), !query ? (_jsx(Action, { href: `${CONSOLE}/${tab}`, target: "_blank", rel: "noreferrer", $touchable: { minH: 44, minW: 44 }, children: "Add in console" })) : null] }))] }));
|
|
292
|
+
}
|
|
293
|
+
function RowCard({ row, tab }) {
|
|
294
|
+
const { isAuthenticated } = useIam();
|
|
295
|
+
/** Hand a channel to the gateway's OAuth and let it say what happened. */
|
|
296
|
+
const connect = () => {
|
|
297
|
+
if (!isAuthenticated) {
|
|
298
|
+
enter();
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
// Same-tab origin for the return leg: the reader lands back on the site
|
|
302
|
+
// they started from rather than on production from a preview build.
|
|
303
|
+
const back = encodeURIComponent(`${window.location.origin}/inbox`);
|
|
304
|
+
window.open(`${api()}/v1/channels/${encodeURIComponent(row.id)}/oauth/authorize?redirect_uri=${back}`, '_blank', 'noopener,noreferrer');
|
|
305
|
+
};
|
|
306
|
+
// Apps open the page this site serves; a plugin or a skill is installed in
|
|
307
|
+
// the console, which is where its registry is. Only a channel has a flow of
|
|
308
|
+
// its own, and only that one is a button.
|
|
309
|
+
const href = tab === 'apps' ? site(`/integrations/${row.id}`) : `${CONSOLE}/${tab}`;
|
|
310
|
+
return (_jsxs(YStack, { gap: "$2", p: "$3", borderWidth: 1, borderColor: "$borderColor", rounded: "$3", height: "100%", bg: "$raised", children: [_jsxs(XStack, { items: "center", gap: "$2", children: [_jsx(ProviderMark, { org: row.id.replace(/-sdk$/, ''), size: 20 }), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(Text, { fontSize: "$3", fontWeight: "500", color: "$ink", numberOfLines: 1, children: row.name }), _jsx(Text, { fontSize: "$2", color: "$soft", numberOfLines: 1, children: row.publisher })] }), row.connected ? (_jsxs(XStack, { items: "center", gap: "$1", px: "$2", py: "$1", rounded: "$2", bg: "$hover", children: [_jsx(Check, { size: 12, color: GOOD, "aria-hidden": true }), _jsx(Text, { fontSize: "$1", color: GOOD, fontWeight: "600", children: "Connected" })] })) : tab === 'channels' ? (_jsx(Box, { render: "button", onClick: connect, "aria-label": `Connect ${row.name}`, px: "$2", py: "$1", rounded: "$2", borderWidth: 1, borderColor: "$borderColor", bg: "var(--white-05)", hoverStyle: { bg: '$hover' }, cursor: "pointer", children: _jsx(Text, { fontSize: "$1", color: "$color", fontWeight: "600", children: "Connect" }) })) : (_jsx(Box, { render: _jsx("a", { href: href, target: tab === 'apps' ? undefined : '_blank', rel: "noreferrer", "aria-label": `Open ${row.name}` }), ...tap, children: _jsxs(XStack, { items: "center", gap: "$1", px: "$2", py: "$1", rounded: "$2", borderWidth: 1, borderColor: "$borderColor", bg: "var(--white-05)", hoverStyle: { bg: '$hover' }, children: [_jsx(Text, { fontSize: "$1", color: "$color", fontWeight: "600", children: tab === 'apps' ? 'Docs' : 'Install' }), _jsx(ArrowUpRight, { size: 11, "aria-hidden": true })] }) }))] }), _jsx(Text, { fontSize: "$2", color: "$soft", numberOfLines: 2, flex: 1, children: row.description }), _jsx(XStack, { items: "center", pt: "$1", children: _jsx(Text, { fontFamily: "$mono", fontVariant: ['tabular-nums'], fontSize: "$1", color: "$soft", children: row.kind }) })] }));
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* One channel: a provider the org can hold an account with.
|
|
314
|
+
*
|
|
315
|
+
* Three verbs and each one is a call. Connect asks the registry to open the
|
|
316
|
+
* provider's consent — it answers the authorize URL, which opens in its own tab
|
|
317
|
+
* so the reader lands back here — and a provider this deployment holds no app
|
|
318
|
+
* credentials for is drawn without a Connect, saying so, because the registry
|
|
319
|
+
* said so. A connected channel can take another account or be disconnected,
|
|
320
|
+
* and what the card shows afterwards is what the registry answers on the next
|
|
321
|
+
* read, never what the press assumed.
|
|
322
|
+
*/
|
|
323
|
+
function Channel({ row, onChange }) {
|
|
324
|
+
const { isAuthenticated, accessToken } = useIam();
|
|
325
|
+
const [busy, setBusy] = useState(false);
|
|
326
|
+
const [wrong, setWrong] = useState(null);
|
|
327
|
+
const post = async (verb) => {
|
|
328
|
+
if (!isAuthenticated || !accessToken) {
|
|
329
|
+
enter();
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
setBusy(true);
|
|
333
|
+
setWrong(null);
|
|
334
|
+
try {
|
|
335
|
+
const r = await fetch(`${api()}/v1/integrations/${encodeURIComponent(row.id)}/${verb}`, {
|
|
336
|
+
method: 'POST',
|
|
337
|
+
headers: { ...scope(), 'Content-Type': 'application/json' },
|
|
338
|
+
body: '{}',
|
|
339
|
+
});
|
|
340
|
+
if (!r.ok)
|
|
341
|
+
throw refusal(r);
|
|
342
|
+
const j = (await r.json());
|
|
343
|
+
if (j.authorizeUrl)
|
|
344
|
+
window.open(j.authorizeUrl, '_blank', 'noopener,noreferrer');
|
|
345
|
+
onChange();
|
|
346
|
+
}
|
|
347
|
+
catch (e) {
|
|
348
|
+
setWrong(e);
|
|
349
|
+
}
|
|
350
|
+
finally {
|
|
351
|
+
setBusy(false);
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
const action = (label, verb, icon) => (_jsxs(Box, { render: "button", onClick: () => void post(verb), "aria-label": `${label} ${row.name}`, "aria-disabled": busy, px: "$3", py: "$1.5", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, opacity: busy ? 0.5 : 1, display: "flex", items: "center", gap: "$1", children: [icon, _jsx(Text, { fontSize: "$2", color: "$ink", fontWeight: "500", children: label })] }));
|
|
355
|
+
return (_jsxs(YStack, { items: "center", gap: "$2", p: "$4", borderWidth: 1, borderColor: "$borderColor", rounded: "$4", bg: "$raised", children: [row.connected ? (_jsxs(XStack, { self: "flex-start", items: "center", gap: "$1", "aria-label": "Connected", children: [_jsx(Box, { width: 6, height: 6, rounded: 3, bg: "$ink" }), _jsx(Text, { fontSize: "$1", color: "$soft", children: "Connected" })] })) : null, _jsxs(YStack, { items: "center", gap: "$2", pt: row.connected ? 0 : '$3', children: [_jsx(ProviderMark, { org: row.id, size: 40 }), _jsx(Text, { fontSize: "$3", fontWeight: "500", color: "$ink", text: "center", numberOfLines: 1, children: row.name }), _jsx(Text, { fontSize: "$1", color: "$soft", text: "center", numberOfLines: 1, children: row.publisher })] }), _jsx(XStack, { gap: "$1.5", items: "center", justify: "center", flexWrap: "wrap", children: row.connected ? (_jsxs(_Fragment, { children: [action('Disconnect', 'disconnect'), row.available ? action('Add', 'connect', _jsx(Plus, { size: 12, "aria-hidden": true })) : null] })) : row.available ? (action('Connect', 'connect')) : null }), wrong ? (_jsx(Text, { fontSize: "$1", color: "$soft", text: "center", children: say(wrong, row.name) })) : null] }));
|
|
356
|
+
}
|
package/dist/Drive.d.ts
ADDED