@fungi.computer/tourtois 0.0.0-stage → 0.0.1

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 (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +127 -2
  3. package/dist/AgentMcpServers.d.ts +34 -0
  4. package/dist/AgentMcpServers.js +124 -0
  5. package/dist/AppAccessConsent.d.ts +23 -0
  6. package/dist/AppAccessConsent.js +122 -0
  7. package/dist/AppCallPermissions.d.ts +9 -0
  8. package/dist/AppCallPermissions.js +21 -0
  9. package/dist/AppDesktopSettings.d.ts +11 -0
  10. package/dist/AppDesktopSettings.js +102 -0
  11. package/dist/AppPane.d.ts +37 -0
  12. package/dist/AppPane.js +184 -0
  13. package/dist/AppWorkbench.d.ts +44 -0
  14. package/dist/AppWorkbench.js +575 -0
  15. package/dist/AvatarPicker.d.ts +7 -0
  16. package/dist/AvatarPicker.js +78 -0
  17. package/dist/AvatarPreview.d.ts +10 -0
  18. package/dist/AvatarPreview.js +6 -0
  19. package/dist/DesktopMenuBar.d.ts +18 -0
  20. package/dist/DesktopMenuBar.js +23 -0
  21. package/dist/FocusedAppMenus.d.ts +2 -0
  22. package/dist/FocusedAppMenus.js +10 -0
  23. package/dist/Keys.d.ts +131 -0
  24. package/dist/Keys.js +201 -0
  25. package/dist/MushroomMark.d.ts +2 -0
  26. package/dist/MushroomMark.js +5 -0
  27. package/dist/SettingsHome.d.ts +18 -0
  28. package/dist/SettingsHome.js +48 -0
  29. package/dist/SettingsNavigation.d.ts +17 -0
  30. package/dist/SettingsNavigation.js +153 -0
  31. package/dist/SystemSettings.d.ts +24 -0
  32. package/dist/SystemSettings.js +177 -0
  33. package/dist/TeamDesktop.d.ts +36 -0
  34. package/dist/TeamDesktop.js +39 -0
  35. package/dist/WorkbenchAppBar.d.ts +5 -0
  36. package/dist/WorkbenchAppBar.js +12 -0
  37. package/dist/app-authority.d.ts +150 -0
  38. package/dist/app-authority.js +41 -0
  39. package/dist/app-directory-reconciliation.d.ts +14 -0
  40. package/dist/app-directory-reconciliation.js +72 -0
  41. package/dist/app-directory.d.ts +206 -0
  42. package/dist/app-directory.js +21 -0
  43. package/dist/app-icon.d.ts +9 -0
  44. package/dist/app-icon.js +27 -0
  45. package/dist/app-launch.d.ts +53 -0
  46. package/dist/app-launch.js +204 -0
  47. package/dist/app-menus.d.ts +6 -0
  48. package/dist/app-menus.js +81 -0
  49. package/dist/app-open-url.d.ts +8 -0
  50. package/dist/app-open-url.js +63 -0
  51. package/dist/app-surface.d.ts +62 -0
  52. package/dist/app-surface.js +10 -0
  53. package/dist/app-windows.d.ts +107 -0
  54. package/dist/app-windows.js +21 -0
  55. package/dist/chrome.css +153 -0
  56. package/dist/command-dialogs.d.ts +42 -0
  57. package/dist/command-dialogs.js +194 -0
  58. package/dist/desktop/Desktop.d.ts +34 -0
  59. package/dist/desktop/Desktop.js +275 -0
  60. package/dist/desktop/consent.d.ts +4 -0
  61. package/dist/desktop/consent.js +64 -0
  62. package/dist/desktop/gill-frame.d.ts +14 -0
  63. package/dist/desktop/gill-frame.js +41 -0
  64. package/dist/desktop/gill-param-picker.d.ts +15 -0
  65. package/dist/desktop/gill-param-picker.js +145 -0
  66. package/dist/desktop/launcher-gill.d.ts +20 -0
  67. package/dist/desktop/launcher-gill.js +49 -0
  68. package/dist/desktop/own-launchers.d.ts +9 -0
  69. package/dist/desktop/own-launchers.js +9 -0
  70. package/dist/desktop/row-gill.d.ts +24 -0
  71. package/dist/desktop/row-gill.js +136 -0
  72. package/dist/desktop-api.d.ts +63 -0
  73. package/dist/desktop-api.js +31 -0
  74. package/dist/desktop-client.d.ts +19 -0
  75. package/dist/desktop-client.js +127 -0
  76. package/dist/desktop-layout.d.ts +117 -0
  77. package/dist/desktop-layout.js +305 -0
  78. package/dist/desktop-visibility.d.ts +8 -0
  79. package/dist/desktop-visibility.js +5 -0
  80. package/dist/layers.d.ts +5 -0
  81. package/dist/layers.js +31 -0
  82. package/dist/main.d.ts +2 -0
  83. package/dist/main.js +17 -0
  84. package/dist/member-avatar-cache.d.ts +15 -0
  85. package/dist/member-avatar-cache.js +69 -0
  86. package/dist/member-avatar.d.ts +39 -0
  87. package/dist/member-avatar.js +88 -0
  88. package/dist/menu-bar.d.ts +5 -0
  89. package/dist/menu-bar.js +5 -0
  90. package/dist/model.d.ts +6 -0
  91. package/dist/model.js +13 -0
  92. package/dist/oyster-platform-controls.d.ts +4 -0
  93. package/dist/oyster-platform-controls.js +107 -0
  94. package/dist/platform-controls.d.ts +84 -0
  95. package/dist/platform-controls.js +266 -0
  96. package/dist/queries.d.ts +7 -0
  97. package/dist/queries.js +7 -0
  98. package/dist/session-registry.d.ts +16 -0
  99. package/dist/session-registry.js +69 -0
  100. package/dist/session-selection.d.ts +6 -0
  101. package/dist/session-selection.js +22 -0
  102. package/dist/settings-leave-guard.d.ts +5 -0
  103. package/dist/settings-leave-guard.js +9 -0
  104. package/dist/settings-navigation.css +129 -0
  105. package/dist/settings-routes.d.ts +118 -0
  106. package/dist/settings-routes.js +216 -0
  107. package/dist/settings-sections.d.ts +74 -0
  108. package/dist/settings-sections.js +17 -0
  109. package/dist/shell-commands.d.ts +15 -0
  110. package/dist/shell-commands.js +95 -0
  111. package/dist/shell-host.d.ts +52 -0
  112. package/dist/shell-host.js +224 -0
  113. package/dist/shell-module.d.ts +12 -0
  114. package/dist/shell-module.js +17 -0
  115. package/dist/shell-release.d.ts +3 -0
  116. package/dist/shell-release.js +4 -0
  117. package/dist/shell.css +858 -0
  118. package/dist/shell.d.ts +6 -0
  119. package/dist/shell.js +83 -0
  120. package/dist/spa.css +11 -0
  121. package/dist/status-bar.d.ts +9 -0
  122. package/dist/status-bar.js +51 -0
  123. package/dist/team-directory.d.ts +3 -0
  124. package/dist/team-directory.js +6 -0
  125. package/dist/use-window-motion.d.ts +3 -0
  126. package/dist/use-window-motion.js +37 -0
  127. package/dist/useAppOpenUrlHandoff.d.ts +11 -0
  128. package/dist/useAppOpenUrlHandoff.js +62 -0
  129. package/dist/useAppWindowManager.d.ts +323 -0
  130. package/dist/useAppWindowManager.js +47 -0
  131. package/dist/useDirectoryCommands.d.ts +12 -0
  132. package/dist/useDirectoryCommands.js +80 -0
  133. package/dist/workbench.d.ts +3 -0
  134. package/dist/workbench.js +2 -0
  135. package/package.json +222 -3
@@ -0,0 +1,153 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Button } from "@fungi.computer/caps/components/button";
3
+ import { Select } from "@fungi.computer/caps/components/select";
4
+ import { AppIcon } from "./app-icon.js";
5
+ import { addressKey, } from "@fungi.computer/oyster/shell/app-directory";
6
+ import { OwnSprite } from "./member-avatar.js";
7
+ export function SettingsNavigation({ location, apps, sections, desktopOnly = false, onNavigate, }) {
8
+ const groups = navigationGroups(apps, sections, desktopOnly);
9
+ const selectedValue = navigationValue(location, apps);
10
+ const selectOption = (value) => {
11
+ const option = groups
12
+ .flatMap(({ options }) => options)
13
+ .find((candidate) => candidate.value === value);
14
+ if (option)
15
+ onNavigate(option.location);
16
+ };
17
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: "tourtois-settings-mobile-select", children: _jsxs(Select, { "aria-label": "Settings category", value: selectedValue, onChange: (event) => {
18
+ selectOption(event.currentTarget.value);
19
+ }, children: [_jsx("option", { value: "", disabled: true, children: "Choose a settings category" }), groups.map(({ label, options }) => (_jsx("optgroup", { label: label, children: options.map(({ value, label: optionLabel }) => (_jsx("option", { value: value, children: optionLabel }, value))) }, label)))] }) }), _jsx("nav", { className: "tourtois-settings-nav", "aria-label": "Settings categories", children: groups.map(({ label, showRailLabel, options }) => (_jsxs("div", { className: "tourtois-settings-nav-group", children: [showRailLabel === false ? null : (_jsx("span", { className: "caps-eyebrow tourtois-settings-nav-label", children: label })), options.map((option) => (_jsxs(Button, { type: "button", size: "sm", variant: "ghost", className: option.value === selectedValue
20
+ ? "bg-base-300 text-base-content"
21
+ : undefined, "aria-pressed": option.value === selectedValue, onClick: () => {
22
+ onNavigate(option.location);
23
+ }, children: [option.icon, _jsx("span", { className: "tourtois-settings-nav-item-label", children: option.label })] }, option.value)))] }, label))) })] }));
24
+ }
25
+ function navigationGroups(apps, sections, desktopOnly) {
26
+ const groups = [
27
+ {
28
+ label: "Settings",
29
+ // The dialog header already reads "System Settings"; don't repeat it
30
+ // as a rail heading above this single "Settings home" item.
31
+ showRailLabel: false,
32
+ options: [
33
+ {
34
+ value: "home",
35
+ label: "Settings home",
36
+ location: { category: "home" },
37
+ },
38
+ ],
39
+ },
40
+ {
41
+ label: "You",
42
+ options: [
43
+ {
44
+ value: "avatar",
45
+ label: "Avatar",
46
+ location: { category: "avatar" },
47
+ icon: (_jsx(OwnSprite, { "aria-hidden": "true", className: "tourtois-settings-nav-sprite" })),
48
+ },
49
+ ],
50
+ },
51
+ {
52
+ label: "Apps",
53
+ options: [
54
+ {
55
+ value: "defaults",
56
+ label: "Default Apps",
57
+ location: { category: "defaults" },
58
+ },
59
+ {
60
+ value: "app-permissions",
61
+ label: desktopOnly ? "App desktop" : "App permissions",
62
+ location: {
63
+ category: "apps",
64
+ appKey: null,
65
+ section: desktopOnly ? "desktop" : "permissions",
66
+ },
67
+ },
68
+ ...apps.map((app) => {
69
+ const key = addressKey(app.address);
70
+ return {
71
+ value: `app:${key}`,
72
+ label: app.displayName ?? app.appId,
73
+ location: {
74
+ category: "apps",
75
+ appKey: key,
76
+ section: desktopOnly
77
+ ? "desktop"
78
+ : "permissions",
79
+ },
80
+ icon: _jsx(AppIcon, { roles: app.roles, icon: app.icon }),
81
+ };
82
+ }),
83
+ ],
84
+ },
85
+ {
86
+ label: "Team",
87
+ options: [
88
+ {
89
+ value: "selling",
90
+ label: "Selling",
91
+ location: { category: "selling", appId: null },
92
+ },
93
+ {
94
+ value: "channels",
95
+ label: "Channels",
96
+ location: { category: "channels" },
97
+ },
98
+ {
99
+ value: "connectors",
100
+ label: "Connectors",
101
+ location: { category: "connectors", target: { kind: "list" } },
102
+ },
103
+ {
104
+ value: "computers",
105
+ label: "Internet access",
106
+ location: { category: "computers" },
107
+ },
108
+ {
109
+ value: "keys",
110
+ label: "Keys",
111
+ location: { category: "keys" },
112
+ },
113
+ ],
114
+ },
115
+ ];
116
+ return groups
117
+ .map((group) => ({
118
+ ...group,
119
+ options: group.options.filter((option) => sections.includes(option.location.category)),
120
+ }))
121
+ .filter((group) => group.options.length > 0);
122
+ }
123
+ function navigationValue(location, apps) {
124
+ switch (location?.category) {
125
+ case "selling":
126
+ return "selling";
127
+ case "home":
128
+ return "home";
129
+ case "avatar":
130
+ return "avatar";
131
+ case "defaults":
132
+ return "defaults";
133
+ case "connectors":
134
+ return "connectors";
135
+ case "computers":
136
+ return "computers";
137
+ case "keys":
138
+ return "keys";
139
+ case "channels":
140
+ return "channels";
141
+ case "apps":
142
+ return location.appKey === null
143
+ ? "app-permissions"
144
+ : appNavigationValue(location.appKey, apps);
145
+ default:
146
+ return "home";
147
+ }
148
+ }
149
+ function appNavigationValue(appKey, apps) {
150
+ if (!appKey || !apps.some((app) => addressKey(app.address) === appKey))
151
+ return "";
152
+ return `app:${appKey}`;
153
+ }
@@ -0,0 +1,24 @@
1
+ import { type AppDesktopSettingsApp } from "./AppDesktopSettings.js";
2
+ import { type AppDirectoryEntry } from "@fungi.computer/oyster/shell/app-directory";
3
+ import type { WorkbenchDesktopSettings } from "@fungi.computer/oyster/shell/workbench";
4
+ import { type SettingsSectionPorts } from "./settings-sections.js";
5
+ import type { SettingsLocation } from "./settings-routes.js";
6
+ /** One system-settings surface. The menu only requests opening it. */
7
+ export declare function SystemSettings({ location: requestedLocation, sections: ceiling, onNavigate, apps, appCount, onAccepted, desktopSettings, desktopConsentReadyAt, onClosed, restoreFocus, ports, }: {
8
+ location: SettingsLocation | null;
9
+ /** The host's navigation ceiling; a section also needs its port. */
10
+ sections?: readonly SettingsLocation["category"][] | undefined;
11
+ onNavigate: (location: SettingsLocation | null) => void;
12
+ apps: readonly (AppDesktopSettingsApp & Pick<AppDirectoryEntry, "address" | "installationId" | "appId" | "displayName" | "icon" | "calls"> & Readonly<{
13
+ roles: readonly string[];
14
+ }>)[];
15
+ /** The directory's App count once read; null is not zero. */
16
+ appCount: number | null;
17
+ onAccepted: () => Promise<void>;
18
+ desktopSettings: WorkbenchDesktopSettings;
19
+ desktopConsentReadyAt: number;
20
+ onClosed: () => void;
21
+ restoreFocus: boolean;
22
+ /** The sections this host implements. */
23
+ ports: SettingsSectionPorts;
24
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,177 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { AppCallPermissions } from "./AppCallPermissions.js";
3
+ import { SectionHeading } from "@fungi.computer/caps/blocks/section-heading";
4
+ import { AppDesktopSettings, } from "./AppDesktopSettings.js";
5
+ import { addressKey, } from "@fungi.computer/oyster/shell/app-directory";
6
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
7
+ import { AgentMcpServers } from "./AgentMcpServers.js";
8
+ import { Keys } from "./Keys.js";
9
+ import { AppIcon } from "./app-icon.js";
10
+ import { AvatarSettings } from "./member-avatar.js";
11
+ import { SettingsNavigation } from "./SettingsNavigation.js";
12
+ import { SettingsHome } from "./SettingsHome.js";
13
+ import { offeredSettings, } from "./settings-sections.js";
14
+ import { ActionRow } from "@fungi.computer/caps/components/action-row";
15
+ import { DataList, DataRow } from "@fungi.computer/caps/components/data-list";
16
+ import { SettingsLeaveContext, } from "./settings-leave-guard.js";
17
+ import { Button } from "@fungi.computer/caps/components/button";
18
+ import { WindowFrame, WindowHeader, WindowActions, } from "@fungi.computer/caps/components/window";
19
+ import { Dialog, DialogContent, DialogTitle, DialogDescription, } from "@fungi.computer/caps/components/dialog";
20
+ /** One system-settings surface. The menu only requests opening it. */
21
+ export function SystemSettings({ location: requestedLocation, sections: ceiling, onNavigate, apps, appCount, onAccepted, desktopSettings, desktopConsentReadyAt, onClosed, restoreFocus, ports, }) {
22
+ const sections = useMemo(() => offeredSettings(ceiling, ports), [ceiling, ports]);
23
+ const location = requestedLocation !== null && sections.includes(requestedLocation.category)
24
+ ? requestedLocation
25
+ : null;
26
+ const { agentMcpServers, keys, appPermissions: AppPermissions, appAdvanced: AppAdvanced, } = ports;
27
+ const avatar = location?.category === "avatar";
28
+ const connectors = location?.category === "connectors";
29
+ const computersSection = location?.category === "computers";
30
+ const keysSection = location?.category === "keys";
31
+ const keyableApps = useMemo(() => apps.flatMap((app) => app.installationId === null
32
+ ? []
33
+ : [
34
+ {
35
+ installationId: app.installationId,
36
+ displayName: app.displayName ?? app.appId,
37
+ },
38
+ ]), [apps]);
39
+ const defaults = location?.category === "defaults";
40
+ const home = location?.category === "home";
41
+ const appSection = location?.category === "apps";
42
+ // Load App editors only after an App is chosen, then retain their drafts
43
+ // while moving between categories in this dialog.
44
+ const [appsLoaded, setAppsLoaded] = useState(false);
45
+ if (appSection && location.appKey !== null && !appsLoaded)
46
+ setAppsLoaded(true);
47
+ const appLocation = location?.category === "apps" ? location : null;
48
+ const appKey = appLocation?.appKey ?? null;
49
+ // A host without a Permissions or Advanced port never renders that tab.
50
+ // Route requests for those sections (the window chrome's shield and gear)
51
+ // land here too, so fold them into Desktop instead of a blank content area.
52
+ const desktopOnly = AppPermissions === undefined;
53
+ const advanced = AppAdvanced !== undefined && appLocation?.section === "advanced";
54
+ const desktop = appLocation !== null &&
55
+ !advanced &&
56
+ (desktopOnly || appLocation.section !== "permissions");
57
+ const selected = apps.find((app) => addressKey(app.address) === appKey);
58
+ const guards = useRef(new Set());
59
+ const register = useCallback((guard) => {
60
+ guards.current.add(guard);
61
+ return () => {
62
+ guards.current.delete(guard);
63
+ };
64
+ }, []);
65
+ const [discard, setDiscard] = useState(false);
66
+ const discardDestination = useRef(null);
67
+ const [leaveError, setLeaveError] = useState(null);
68
+ const canClose = () => {
69
+ const states = [...guards.current].map((read) => read());
70
+ if (states.includes("pending") || states.includes("uncertain")) {
71
+ setLeaveError(states.includes("pending")
72
+ ? "Wait for the current request to finish before closing."
73
+ : "Retry the uncertain request before closing so its request identity is preserved.");
74
+ return false;
75
+ }
76
+ setLeaveError(null);
77
+ return true;
78
+ };
79
+ useEffect(() => {
80
+ if (!location)
81
+ return;
82
+ const beforeUnload = (event) => {
83
+ if ([...guards.current].some((read) => read() !== "clean"))
84
+ event.preventDefault();
85
+ };
86
+ window.addEventListener("beforeunload", beforeUnload);
87
+ return () => {
88
+ window.removeEventListener("beforeunload", beforeUnload);
89
+ };
90
+ }, [location]);
91
+ const requestNavigation = (next) => {
92
+ if (!canClose())
93
+ return;
94
+ if ([...guards.current].some((read) => read() === "dirty")) {
95
+ discardDestination.current = next;
96
+ setDiscard(true);
97
+ return;
98
+ }
99
+ onNavigate(next);
100
+ };
101
+ const close = () => {
102
+ requestNavigation(null);
103
+ };
104
+ const headerProps = {
105
+ title: "System Settings",
106
+ actions: _jsx(WindowActions, { title: "System Settings", onClose: close }),
107
+ };
108
+ if (location?.category === "selling" && location.appId !== null)
109
+ headerProps.back = {
110
+ label: "Back to Selling",
111
+ onBack: () => {
112
+ requestNavigation({ category: "selling", appId: null });
113
+ },
114
+ };
115
+ return (_jsx(Dialog, { open: location !== null, onOpenChangeComplete: (open) => {
116
+ if (!open) {
117
+ setAppsLoaded(false);
118
+ onClosed();
119
+ }
120
+ }, onOpenChange: (next) => {
121
+ if (!next)
122
+ close();
123
+ }, children: _jsxs(DialogContent, { finalFocus: restoreFocus, render: _jsx(WindowFrame, { floating: true }), keepMounted: false, className: "tourtois-system-settings", children: [_jsx(DialogTitle, { className: "sr-only", children: "System Settings" }), _jsx(DialogDescription, { className: "sr-only", children: desktopOnly
124
+ ? "Your desktop and App settings."
125
+ : "Your avatar, Channels, Connectors, App access and App settings." }), _jsx(WindowHeader, { ...headerProps }), _jsx(SettingsLeaveContext.Provider, { value: register, children: _jsxs("div", { className: "tourtois-settings-layout", children: [_jsx(SettingsNavigation, { sections: sections, desktopOnly: desktopOnly, location: location, apps: apps, onNavigate: (next) => {
126
+ if (location?.category === "selling" && location.appId !== null)
127
+ requestNavigation(next);
128
+ else
129
+ onNavigate(next);
130
+ } }), home ? (_jsx(SettingsHome, { sections: sections, desktopOnly: desktopOnly, team: ports.team, summary: {
131
+ appCount,
132
+ defaultAppCount: ports.summary?.defaultAppCount ?? null,
133
+ computerCount: ports.summary?.computerCount ?? null,
134
+ }, onNavigate: onNavigate })) : null, avatar && ports.avatar !== undefined ? (_jsx("div", { className: "tourtois-settings-content", children: _jsx(AvatarSettings, { save: ports.avatar.save }) })) : null, connectors && agentMcpServers !== undefined ? (_jsx(AgentMcpServers, { port: agentMcpServers })) : null, connectors &&
135
+ agentMcpServers === undefined &&
136
+ ports.connectors !== undefined ? (_jsx(ports.connectors, { target: location.target, onNavigate: onNavigate })) : null, location?.category === "selling" && ports.selling !== undefined ? (_jsx(ports.selling, { appId: location.appId, onNavigate: onNavigate })) : null, computersSection && ports.computers !== undefined ? (_jsx("div", { className: "tourtois-settings-content", children: _jsx(ports.computers, {}) })) : null, keysSection && keys !== undefined ? (_jsx(Keys, { port: keys, apps: keyableApps })) : null, location?.category === "channels" &&
137
+ ports.channels !== undefined ? (_jsx(ports.channels, {})) : null, defaults && ports.defaults !== undefined ? (_jsx("div", { className: "tourtois-settings-content", children: _jsx(ports.defaults, { onAccepted: onAccepted }) })) : null, appSection && appKey === null ? (_jsxs("section", { className: "tourtois-settings-content", "aria-label": "Choose an App", children: [_jsx(SectionHeading, { className: "tourtois-settings-app-heading", children: desktopOnly ? "App desktop" : "App permissions" }), _jsx(DataList, { density: "compact", "aria-label": "Apps to review", children: apps.map((app) => (_jsx(DataRow, { className: "p-0", children: _jsx(ActionRow, { label: app.displayName ?? app.appId, icon: _jsx(AppIcon, { roles: app.roles, icon: app.icon }), onClick: () => {
138
+ onNavigate({
139
+ category: "apps",
140
+ appKey: addressKey(app.address),
141
+ section: desktopOnly ? "desktop" : "permissions",
142
+ });
143
+ } }) }, addressKey(app.address)))) }), appCount === 0 ? (_jsx("p", { children: desktopOnly
144
+ ? "No Apps installed yet."
145
+ : "No Apps in this Team yet." })) : null] })) : null, appsLoaded ? (_jsxs("section", { className: "tourtois-settings-content", "aria-label": "App settings", hidden: !appSection || appKey === null, children: [_jsxs("div", { className: "tourtois-settings-app-header", children: [_jsxs(SectionHeading, { className: "tourtois-settings-app-heading", children: [selected ? (_jsx(AppIcon, { roles: selected.roles, icon: selected.icon })) : null, selected
146
+ ? (selected.displayName ?? selected.appId)
147
+ : "Apps"] }), selected ? (_jsxs("div", { className: "tourtois-settings-app-tabs", role: "group", "aria-label": "App settings sections", children: [!desktopOnly ? (_jsx(Button, { type: "button", size: "sm", variant: "ghost", className: !advanced && !desktop
148
+ ? "bg-base-300 text-base-content"
149
+ : undefined, "aria-pressed": !advanced && !desktop, onClick: () => {
150
+ onNavigate({
151
+ category: "apps",
152
+ appKey,
153
+ section: "permissions",
154
+ });
155
+ }, children: "Permissions" })) : null, _jsx(Button, { type: "button", size: "sm", variant: "ghost", className: desktop ? "bg-base-300 text-base-content" : undefined, "aria-pressed": desktop, onClick: () => {
156
+ onNavigate({
157
+ category: "apps",
158
+ appKey,
159
+ section: "desktop",
160
+ });
161
+ }, children: "Desktop" }), AppAdvanced !== undefined ? (_jsx(Button, { type: "button", size: "sm", variant: "ghost", className: advanced
162
+ ? "bg-base-300 text-base-content"
163
+ : undefined, "aria-pressed": advanced, onClick: () => {
164
+ onNavigate({
165
+ category: "apps",
166
+ appKey,
167
+ section: "advanced",
168
+ });
169
+ }, children: "Advanced" })) : null] })) : null] }), _jsxs("div", { className: "tourtois-settings-app-content", children: [!selected ? (_jsx("p", { children: "Choose an app to manage its permissions and advanced settings." })) : null, _jsx("div", { className: "tourtois-settings-permissions", hidden: !selected || advanced || desktop, children: AppPermissions !== undefined ? (_jsx(AppPermissions, { selectedKey: appKey, onAccepted: onAccepted })) : null }), selected && desktop && desktopOnly ? (_jsx(AppCallPermissions, { calls: selected.calls, apps: apps })) : null, selected && desktop ? (_jsx(AppDesktopSettings, { app: selected, desktop: desktopSettings, consentReadyAt: desktopConsentReadyAt }, selected.surfaceId)) : null, AppAdvanced !== undefined ? (_jsx(AppAdvanced, { selectedKey: appKey, visible: advanced, onNavigate: onNavigate, onAccepted: onAccepted })) : null] })] })) : null] }) }), leaveError ? (_jsx("div", { className: "tourtois-settings-discard", role: "alert", children: leaveError })) : null, discard ? (_jsxs("div", { className: "tourtois-settings-discard", role: "alert", children: [_jsx("p", { children: "Discard unsaved changes?" }), _jsxs("div", { className: "tourtois-settings-actions", children: [_jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => {
170
+ setDiscard(false);
171
+ }, children: "Keep editing" }), _jsx(Button, { type: "button", size: "sm", onClick: () => {
172
+ if (!canClose())
173
+ return;
174
+ setDiscard(false);
175
+ onNavigate(discardDestination.current);
176
+ }, children: "Discard changes" })] })] })) : null] }) }));
177
+ }
@@ -0,0 +1,36 @@
1
+ import type { DesktopMethods } from "./desktop-api.js";
2
+ import type { ComputerOption } from "./app-launch.js";
3
+ import type { SettingsLocation } from "./settings-routes.js";
4
+ import type { ReactNode } from "react";
5
+ import type { AppBarItem } from "@fungi.computer/caps/blocks/app-bar";
6
+ import type { WorkbenchDirectory } from "@fungi.computer/oyster/shell/workbench-directory";
7
+ import type { PlatformControls } from "./platform-controls.js";
8
+ import type { AppStorage } from "@fungi.computer/agar/storage";
9
+ import type { DesktopGillTransport } from "./desktop/row-gill.js";
10
+ import type { AppOpenInput } from "@fungi.computer/agar/apps";
11
+ export type TeamTourtoisProps = Readonly<{
12
+ teamId: string;
13
+ /** Local host views share the shell layers without giving the dashboard their authority. */
14
+ children?: ReactNode;
15
+ teamName?: string;
16
+ extraLaunchers?: readonly AppBarItem[];
17
+ settingsLaunchers?: readonly Readonly<{
18
+ id: string;
19
+ label: string;
20
+ icon?: ReactNode;
21
+ location: SettingsLocation;
22
+ }>[];
23
+ systemMenuItems?: ReactNode;
24
+ /** Read the Team's Computers through the host's `computers()` port. */
25
+ computersReader: (signal: AbortSignal) => Promise<readonly ComputerOption[]>;
26
+ /** Read the Team's App directory through the host's `directory()` port. */
27
+ directoryReader: () => Promise<WorkbenchDirectory>;
28
+ /** Take the host address's App-open handoff once, instead of this URL's. */
29
+ takeAppOpenRequest?: () => Promise<AppOpenInput | null>;
30
+ /** Team administration, rendered by the host with the owner's authority. */
31
+ platform: PlatformControls;
32
+ desktopStorage: AppStorage;
33
+ desktopMethods: DesktopMethods;
34
+ gills: DesktopGillTransport;
35
+ }>;
36
+ export declare function TeamTourtois({ children, teamId, teamName, extraLaunchers, settingsLaunchers, systemMenuItems, computersReader, directoryReader, takeAppOpenRequest, platform, desktopStorage, desktopMethods, gills, }: TeamTourtoisProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,39 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { tourtoisQueryKeys } from "./queries.js";
3
+ import { TourtoisLayers } from "./layers.js";
4
+ import { useQuery } from "@tanstack/react-query";
5
+ import { MaosaicoRoot } from "@fungi.computer/maosaico";
6
+ import { useEffect, useRef, useState } from "react";
7
+ import { AppWorkbench } from "./AppWorkbench.js";
8
+ function useWorkbenchBounds() {
9
+ const ref = useRef(null);
10
+ const [bounds, setBounds] = useState({ height: 0, width: 0 });
11
+ useEffect(() => {
12
+ const element = ref.current;
13
+ if (!element)
14
+ return;
15
+ const observer = new ResizeObserver(([entry]) => {
16
+ setBounds({
17
+ height: Math.max(0, entry?.contentRect.height ?? 0),
18
+ width: Math.max(0, entry?.contentRect.width ?? 0),
19
+ });
20
+ });
21
+ observer.observe(element);
22
+ return () => {
23
+ observer.disconnect();
24
+ };
25
+ }, []);
26
+ return { bounds, ref };
27
+ }
28
+ export function TeamTourtois({ children, teamId, teamName, extraLaunchers, settingsLaunchers, systemMenuItems, computersReader, directoryReader, takeAppOpenRequest, platform, desktopStorage, desktopMethods, gills, }) {
29
+ const { bounds, ref: paneLayerRef } = useWorkbenchBounds();
30
+ const phone = bounds.width <= 540;
31
+ const [tiled, setTiled] = useState(true);
32
+ const { data, isError, isLoading } = useQuery({
33
+ queryKey: tourtoisQueryKeys.workbenchComputers(teamId),
34
+ queryFn: ({ signal }) => computersReader(signal),
35
+ });
36
+ return (_jsx("main", { className: "tourtois-shell", "data-tourtois-shell": true, children: _jsxs(TourtoisLayers, { children: [_jsx("div", { className: "tourtois-workbench", children: _jsx(MaosaicoRoot, { bounds: bounds, containerRef: paneLayerRef, tiling: tiled || phone
37
+ ? { mainSize: Math.max(0, Math.min(640, bounds.width)), gap: 8 }
38
+ : undefined, children: _jsx(AppWorkbench, { paneLayerRef: paneLayerRef, phone: phone, teamId: teamId, teamName: teamName ?? teamId, initialComputerId: data?.[0]?.computerId ?? null, computers: data ?? [], computersUnavailable: isLoading || isError, onLayoutChange: phone ? undefined : setTiled, platform: platform, desktopStorage: desktopStorage, desktopMethods: desktopMethods, gills: gills, ...(extraLaunchers ? { extraLaunchers } : {}), ...(settingsLaunchers ? { settingsLaunchers } : {}), ...(systemMenuItems ? { systemMenuItems } : {}), directoryReader: directoryReader, ...(takeAppOpenRequest ? { takeAppOpenRequest } : {}) }) }) }), children] }) }));
39
+ }
@@ -0,0 +1,5 @@
1
+ import { type AppBarProps } from "@fungi.computer/caps/blocks/app-bar";
2
+ /** Disclosure only; the supplied callbacks retain window lifecycle ownership. */
3
+ export declare function WorkbenchAppBar({ phone, ...props }: AppBarProps & {
4
+ phone: boolean;
5
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { AppBar } from "@fungi.computer/caps/blocks/app-bar";
3
+ import { IconButton } from "@fungi.computer/caps/components/icon-button";
4
+ import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, } from "@fungi.computer/caps/components/dropdown-menu";
5
+ import { useLayer } from "@fungi.computer/maosaico";
6
+ import { Check, PanelsTopLeft } from "lucide-react";
7
+ /** Disclosure only; the supplied callbacks retain window lifecycle ownership. */
8
+ export function WorkbenchAppBar({ phone, ...props }) {
9
+ const popover = useLayer("popover");
10
+ const collapsed = phone && props.windows.length > 0;
11
+ return (_jsxs(_Fragment, { children: [collapsed ? (_jsxs(DropdownMenu, { modal: false, children: [_jsx(DropdownMenuTrigger, { render: _jsx(IconButton, { size: "responsive", icon: props.windows.find((item) => item.active)?.icon ?? (_jsx(PanelsTopLeft, {})), "aria-label": "Switch open Apps" }), "aria-label": "Switch open Apps", title: "Switch open Apps" }), _jsxs(DropdownMenuContent, { container: popover, align: "start", children: [props.windows.map((item) => (_jsxs(DropdownMenuItem, { role: "menuitemradio", "aria-checked": item.active ?? false, "aria-current": item.active ? "page" : undefined, onSelect: item.onSelect, children: [_jsx("span", { "aria-hidden": "true", className: "grid size-7 shrink-0 place-items-center [&_svg]:size-5", children: item.icon }), _jsx("span", { className: "flex-1", children: item.label }), item.active ? _jsx(Check, { "aria-hidden": "true", size: 16 }) : null] }, item.id))), _jsx(DropdownMenuSeparator, {}), props.windows.map((item) => item.onClose ? (_jsxs(DropdownMenuItem, { onSelect: item.onClose, children: ["Close ", item.label] }, item.id)) : null)] })] })) : null, _jsx(AppBar, { ...props, windows: collapsed ? [] : props.windows })] }));
12
+ }
@@ -0,0 +1,150 @@
1
+ import * as z from "zod";
2
+ import { type AppAddress } from "@fungi.computer/oyster/shell/app-directory";
3
+ export declare const appPermissionChangesSchema: z.ZodObject<{
4
+ computer: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
5
+ kind: z.ZodLiteral<"selected-computers">;
6
+ computers: z.ZodArray<z.ZodObject<{
7
+ computerId: z.ZodString;
8
+ operations: z.ZodArray<z.ZodEnum<{
9
+ "computer.list": "computer.list";
10
+ "computer.readFile": "computer.readFile";
11
+ "computer.writeFile": "computer.writeFile";
12
+ "computer.execute": "computer.execute";
13
+ }>>;
14
+ }, z.core.$strict>>;
15
+ }, z.core.$strict>, z.ZodObject<{
16
+ kind: z.ZodLiteral<"all-team-computers">;
17
+ operations: z.ZodArray<z.ZodEnum<{
18
+ "computer.list": "computer.list";
19
+ "computer.readFile": "computer.readFile";
20
+ "computer.writeFile": "computer.writeFile";
21
+ "computer.execute": "computer.execute";
22
+ }>>;
23
+ }, z.core.$strict>], "kind">>;
24
+ agents: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
25
+ kind: z.ZodLiteral<"all-team-agents">;
26
+ directoryOperations: z.ZodArray<z.ZodEnum<{
27
+ "agent.list": "agent.list";
28
+ }>>;
29
+ sessionOperations: z.ZodArray<z.ZodEnum<{
30
+ "agent.read": "agent.read";
31
+ "agent.send": "agent.send";
32
+ "agent.manage": "agent.manage";
33
+ }>>;
34
+ }, z.core.$strict>, z.ZodObject<{
35
+ kind: z.ZodLiteral<"selected-agents">;
36
+ directoryOperations: z.ZodArray<z.ZodEnum<{
37
+ "agent.list": "agent.list";
38
+ }>>;
39
+ agents: z.ZodArray<z.ZodObject<{
40
+ agentId: z.ZodString;
41
+ sessionOperations: z.ZodArray<z.ZodEnum<{
42
+ "agent.read": "agent.read";
43
+ "agent.send": "agent.send";
44
+ "agent.manage": "agent.manage";
45
+ }>>;
46
+ }, z.core.$strict>>;
47
+ }, z.core.$strict>], "kind">>;
48
+ generation: z.ZodOptional<z.ZodObject<{
49
+ llm: z.ZodBoolean;
50
+ media: z.ZodBoolean;
51
+ }, z.core.$strict>>;
52
+ marketplace: z.ZodOptional<z.ZodObject<{
53
+ browse: z.ZodBoolean;
54
+ purchase: z.ZodBoolean;
55
+ installFree: z.ZodBoolean;
56
+ }, z.core.$strict>>;
57
+ }, z.core.$strict>;
58
+ export type AppPermissionChanges = z.output<typeof appPermissionChangesSchema>;
59
+ export declare const appPermissionReceiptSchema: z.ZodObject<{
60
+ authorityVersion: z.ZodNumber;
61
+ computerPermission: z.ZodNullable<z.ZodDiscriminatedUnion<[z.ZodObject<{
62
+ kind: z.ZodLiteral<"selected-computers">;
63
+ computers: z.ZodArray<z.ZodObject<{
64
+ computerId: z.ZodString;
65
+ operations: z.ZodArray<z.ZodEnum<{
66
+ "computer.list": "computer.list";
67
+ "computer.readFile": "computer.readFile";
68
+ "computer.writeFile": "computer.writeFile";
69
+ "computer.execute": "computer.execute";
70
+ }>>;
71
+ }, z.core.$strict>>;
72
+ }, z.core.$strict>, z.ZodObject<{
73
+ kind: z.ZodLiteral<"all-team-computers">;
74
+ operations: z.ZodArray<z.ZodEnum<{
75
+ "computer.list": "computer.list";
76
+ "computer.readFile": "computer.readFile";
77
+ "computer.writeFile": "computer.writeFile";
78
+ "computer.execute": "computer.execute";
79
+ }>>;
80
+ }, z.core.$strict>], "kind">>;
81
+ agentPermission: z.ZodDiscriminatedUnion<[z.ZodObject<{
82
+ kind: z.ZodLiteral<"all-team-agents">;
83
+ directoryOperations: z.ZodArray<z.ZodEnum<{
84
+ "agent.list": "agent.list";
85
+ }>>;
86
+ sessionOperations: z.ZodArray<z.ZodEnum<{
87
+ "agent.read": "agent.read";
88
+ "agent.send": "agent.send";
89
+ "agent.manage": "agent.manage";
90
+ }>>;
91
+ }, z.core.$strict>, z.ZodObject<{
92
+ kind: z.ZodLiteral<"selected-agents">;
93
+ directoryOperations: z.ZodArray<z.ZodEnum<{
94
+ "agent.list": "agent.list";
95
+ }>>;
96
+ agents: z.ZodArray<z.ZodObject<{
97
+ agentId: z.ZodString;
98
+ sessionOperations: z.ZodArray<z.ZodEnum<{
99
+ "agent.read": "agent.read";
100
+ "agent.send": "agent.send";
101
+ "agent.manage": "agent.manage";
102
+ }>>;
103
+ }, z.core.$strict>>;
104
+ }, z.core.$strict>], "kind">;
105
+ generationPermissions: z.ZodObject<{
106
+ llm: z.ZodBoolean;
107
+ media: z.ZodBoolean;
108
+ }, z.core.$strict>;
109
+ marketplacePermissions: z.ZodObject<{
110
+ browse: z.ZodBoolean;
111
+ purchase: z.ZodBoolean;
112
+ installFree: z.ZodBoolean;
113
+ }, z.core.$strict>;
114
+ }, z.core.$strip>;
115
+ export declare function appPermissionsMutationRequest(teamId: string, requestId: string, address: AppAddress, expectedAuthorityVersion: number, permissions: AppPermissionChanges): {
116
+ url: string;
117
+ init: {
118
+ method: string;
119
+ credentials: "include";
120
+ headers: {
121
+ "Content-Type": string;
122
+ Origin: string;
123
+ "X-Request-Id": string;
124
+ };
125
+ body: string;
126
+ };
127
+ };
128
+ export type AppMutationRequest = ReturnType<typeof appPermissionsMutationRequest>;
129
+ export declare const appMutationResponseSchema: z.ZodObject<{
130
+ kind: z.ZodLiteral<"accepted">;
131
+ receipt: z.ZodUnknown;
132
+ enforcement: z.ZodNullable<z.ZodObject<{
133
+ requestId: z.ZodString;
134
+ address: z.ZodUnion<readonly [z.ZodObject<{
135
+ kind: z.ZodLiteral<"default">;
136
+ appId: z.ZodString;
137
+ }, z.core.$strict>, z.ZodObject<{
138
+ kind: z.ZodLiteral<"installation">;
139
+ installationId: z.ZodString;
140
+ }, z.core.$strict>]>;
141
+ authorityVersion: z.ZodNumber;
142
+ state: z.ZodEnum<{
143
+ unaffected: "unaffected";
144
+ pending: "pending";
145
+ applied: "applied";
146
+ expired: "expired";
147
+ }>;
148
+ outstanding: z.ZodNumber;
149
+ }, z.core.$strict>>;
150
+ }, z.core.$strict>;
@@ -0,0 +1,41 @@
1
+ import * as z from "zod";
2
+ import { appAuthorityEnforcementSchema, appComputerPermissionSchema, appAgentPermissionSchema, appGenerationPermissionsSchema, appMarketplacePermissionsSchema, } from "@fungi.computer/oyster/shell/app-directory";
3
+ export const appPermissionChangesSchema = z
4
+ .strictObject({
5
+ computer: appComputerPermissionSchema.optional(),
6
+ agents: appAgentPermissionSchema.optional(),
7
+ generation: appGenerationPermissionsSchema.optional(),
8
+ marketplace: appMarketplacePermissionsSchema.optional(),
9
+ })
10
+ .refine((changes) => Object.values(changes).some((value) => value !== undefined), "At least one permission section must change");
11
+ export const appPermissionReceiptSchema = z.object({
12
+ authorityVersion: z.number().int().positive(),
13
+ computerPermission: appComputerPermissionSchema.nullable(),
14
+ agentPermission: appAgentPermissionSchema,
15
+ generationPermissions: appGenerationPermissionsSchema,
16
+ marketplacePermissions: appMarketplacePermissionsSchema,
17
+ });
18
+ export function appPermissionsMutationRequest(teamId, requestId, address, expectedAuthorityVersion, permissions) {
19
+ return {
20
+ url: "/api/teams/" + encodeURIComponent(teamId) + "/apps",
21
+ init: {
22
+ method: "POST",
23
+ credentials: "include",
24
+ headers: {
25
+ "Content-Type": "application/json",
26
+ Origin: window.location.origin,
27
+ "X-Request-Id": requestId,
28
+ },
29
+ body: JSON.stringify({
30
+ target: address,
31
+ expectedVersion: expectedAuthorityVersion,
32
+ command: { kind: "set-permissions", permissions },
33
+ }),
34
+ },
35
+ };
36
+ }
37
+ export const appMutationResponseSchema = z.strictObject({
38
+ kind: z.literal("accepted"),
39
+ receipt: z.unknown(),
40
+ enforcement: appAuthorityEnforcementSchema.nullable(),
41
+ });