@ossy/package-catalog 1.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.
- package/package.json +33 -0
- package/src/ActionCapabilityCard.jsx +87 -0
- package/src/ActionExamplesPanel.jsx +47 -0
- package/src/CatalogPageStyles.jsx +15 -0
- package/src/Definition.js +9 -0
- package/src/HowToCard.jsx +79 -0
- package/src/ManifestSection.jsx +259 -0
- package/src/PackageActionsSection.jsx +52 -0
- package/src/PackageHero.jsx +97 -0
- package/src/PackageHowToSection.jsx +99 -0
- package/src/PackageServiceToggle.jsx +69 -0
- package/src/PackagesBrowser.jsx +294 -0
- package/src/WidgetCapabilityCard.jsx +28 -0
- package/src/en.translations.json +85 -0
- package/src/formatPaths.js +69 -0
- package/src/index.js +9 -0
- package/src/package-detail.page.jsx +197 -0
- package/src/packageCopy.js +194 -0
- package/src/packageDetailSections.js +17 -0
- package/src/packageStoreStyles.js +305 -0
- package/src/packages.page.jsx +15 -0
- package/src/sv.translations.json +85 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Text, View } from '@ossy/design-system'
|
|
3
|
+
import { HowToCard } from './HowToCard.jsx'
|
|
4
|
+
import { pkgStoreEnvelopedSurfaceProps } from './packageStoreStyles.js'
|
|
5
|
+
import { PKG_DETAIL_SECTION_IDS } from './packageDetailSections.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Package-tailored interaction overview — descriptions link to sections below.
|
|
9
|
+
*
|
|
10
|
+
* @param {{ pkg: {
|
|
11
|
+
* slug: string
|
|
12
|
+
* actions?: Array<{ id: string }>
|
|
13
|
+
* components?: Array<{ id: string }>
|
|
14
|
+
* aggregates?: Array<{ id: string }>
|
|
15
|
+
* }
|
|
16
|
+
* backgroundTasks?: Array<{ id: string }>
|
|
17
|
+
* }} props
|
|
18
|
+
*/
|
|
19
|
+
export function PackageHowToSection ({ pkg, backgroundTasks = [] }) {
|
|
20
|
+
const isBooking = pkg.slug === 'booking'
|
|
21
|
+
|
|
22
|
+
/** @type {Array<{ icon: string, title: string, body: string, sectionId: string, bodyParams?: Record<string, unknown> }>} */
|
|
23
|
+
const cards = []
|
|
24
|
+
|
|
25
|
+
if (pkg.actions?.length) {
|
|
26
|
+
cards.push(
|
|
27
|
+
{
|
|
28
|
+
icon: 'bolt',
|
|
29
|
+
title: 'package-catalog.home.howTo.sdk.title',
|
|
30
|
+
body: 'package-catalog.detail.howTo.sdk.body',
|
|
31
|
+
sectionId: PKG_DETAIL_SECTION_IDS.actions,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
icon: 'link',
|
|
35
|
+
title: 'package-catalog.home.howTo.mcp.title',
|
|
36
|
+
body: isBooking
|
|
37
|
+
? 'package-catalog.detail.howTo.mcp.booking.body'
|
|
38
|
+
: 'package-catalog.detail.howTo.mcp.body',
|
|
39
|
+
sectionId: PKG_DETAIL_SECTION_IDS.actions,
|
|
40
|
+
bodyParams: isBooking ? undefined : { slug: pkg.slug },
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
icon: 'key',
|
|
44
|
+
title: 'package-catalog.home.howTo.http.title',
|
|
45
|
+
body: 'package-catalog.detail.howTo.http.body',
|
|
46
|
+
sectionId: PKG_DETAIL_SECTION_IDS.actions,
|
|
47
|
+
},
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (pkg.components?.length) {
|
|
52
|
+
cards.push({
|
|
53
|
+
icon: 'layout-grid',
|
|
54
|
+
title: 'package-catalog.detail.howTo.widgets.title',
|
|
55
|
+
body: 'package-catalog.detail.howTo.widgets.body',
|
|
56
|
+
sectionId: PKG_DETAIL_SECTION_IDS.widgets,
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (backgroundTasks.length) {
|
|
61
|
+
cards.push({
|
|
62
|
+
icon: 'timer',
|
|
63
|
+
title: 'package-catalog.detail.howTo.tasks.title',
|
|
64
|
+
body: 'package-catalog.detail.howTo.tasks.body',
|
|
65
|
+
sectionId: PKG_DETAIL_SECTION_IDS.tasks,
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (pkg.aggregates?.length) {
|
|
70
|
+
cards.push({
|
|
71
|
+
icon: 'stack',
|
|
72
|
+
title: 'package-catalog.detail.howTo.aggregates.title',
|
|
73
|
+
body: 'package-catalog.detail.howTo.aggregates.body',
|
|
74
|
+
sectionId: PKG_DETAIL_SECTION_IDS.aggregates,
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!cards.length) return null
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<View {...pkgStoreEnvelopedSurfaceProps}>
|
|
82
|
+
<Text as="h2" variant="heading-secondary" text="package-catalog.detail.howTo.title" />
|
|
83
|
+
<Text text="package-catalog.detail.howTo.intro" className="pkg-store-prose--muted" />
|
|
84
|
+
|
|
85
|
+
<View gap="m" layout="row-wrap">
|
|
86
|
+
{cards.map((card) => (
|
|
87
|
+
<HowToCard
|
|
88
|
+
key={`${card.sectionId}-${card.title}`}
|
|
89
|
+
icon={card.icon}
|
|
90
|
+
title={card.title}
|
|
91
|
+
body={card.body}
|
|
92
|
+
sectionId={card.sectionId}
|
|
93
|
+
bodyParams={card.bodyParams}
|
|
94
|
+
/>
|
|
95
|
+
))}
|
|
96
|
+
</View>
|
|
97
|
+
</View>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { Button } from '@ossy/design-system'
|
|
3
|
+
import { useApp } from '@ossy/app/shell'
|
|
4
|
+
import { GetWorkspace, EnableService, DisableService } from '@ossy/workspaces'
|
|
5
|
+
import { useSdk, cacheKey } from '@ossy/sdk-react'
|
|
6
|
+
import { isServiceEntitled, isToggleablePackage } from '@ossy/workspaces/entitlements'
|
|
7
|
+
import { resolveWorkspaceServices } from '@ossy/app/shell'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Enable or disable a workspace service from the package detail page.
|
|
11
|
+
*/
|
|
12
|
+
export function PackageServiceToggle ({ packageName, entitlementRequired = true }) {
|
|
13
|
+
const app = useApp()
|
|
14
|
+
const sdk = useSdk()
|
|
15
|
+
const { data: workspace, refetch: refetchWorkspace } = sdk.read(GetWorkspace)
|
|
16
|
+
const [busy, setBusy] = useState(false)
|
|
17
|
+
|
|
18
|
+
if (!app?.isAuthenticated || !app?.workspaceId) return null
|
|
19
|
+
if (!packageName || !isToggleablePackage(packageName, { entitlementRequired })) return null
|
|
20
|
+
|
|
21
|
+
const services = resolveWorkspaceServices({
|
|
22
|
+
devMode: app.devMode,
|
|
23
|
+
devEntitlements: app.devEntitlements,
|
|
24
|
+
workspaceServices: workspace?.services,
|
|
25
|
+
})
|
|
26
|
+
const enabled = isServiceEntitled(services, packageName)
|
|
27
|
+
|
|
28
|
+
const toggleService = async (enable) => {
|
|
29
|
+
setBusy(true)
|
|
30
|
+
try {
|
|
31
|
+
const action = enable ? EnableService : DisableService
|
|
32
|
+
await sdk.invoke(action, { service: packageName })
|
|
33
|
+
sdk.invalidate(cacheKey(GetWorkspace))
|
|
34
|
+
await refetchWorkspace()
|
|
35
|
+
} finally {
|
|
36
|
+
setBusy(false)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (enabled) {
|
|
41
|
+
return (
|
|
42
|
+
<Button
|
|
43
|
+
variant="neutral"
|
|
44
|
+
size="s"
|
|
45
|
+
disabled={busy}
|
|
46
|
+
data-action={DisableService.id}
|
|
47
|
+
data-service={packageName}
|
|
48
|
+
data-package-service={packageName}
|
|
49
|
+
data-service-enabled="true"
|
|
50
|
+
onClick={() => toggleService(false)}
|
|
51
|
+
label="package-catalog.detail.service.disable"
|
|
52
|
+
/>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<Button
|
|
58
|
+
variant="cta"
|
|
59
|
+
size="s"
|
|
60
|
+
disabled={busy}
|
|
61
|
+
data-action={EnableService.id}
|
|
62
|
+
data-service={packageName}
|
|
63
|
+
data-package-service={packageName}
|
|
64
|
+
data-service-enabled="false"
|
|
65
|
+
onClick={() => toggleService(true)}
|
|
66
|
+
label="package-catalog.detail.service.enable"
|
|
67
|
+
/>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import React, { useMemo, useState } from 'react'
|
|
2
|
+
import { Button, Icon, Input, Page, ContentHeader, SdkInvokeExample, Text, View } from '@ossy/design-system'
|
|
3
|
+
import { useApp } from '@ossy/app/shell'
|
|
4
|
+
import { useRouter } from '@ossy/router-react'
|
|
5
|
+
import { PackageHero } from './PackageHero.jsx'
|
|
6
|
+
import { CatalogPageStyles } from './CatalogPageStyles.jsx'
|
|
7
|
+
import { HowToCard } from './HowToCard.jsx'
|
|
8
|
+
import {
|
|
9
|
+
ActionCapabilityCard,
|
|
10
|
+
} from './ActionCapabilityCard.jsx'
|
|
11
|
+
import {
|
|
12
|
+
getPackageIcon,
|
|
13
|
+
getPackagePitch,
|
|
14
|
+
humanizeCapabilityId,
|
|
15
|
+
slugToDisplayName,
|
|
16
|
+
} from './packageCopy.js'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {object} pkg
|
|
20
|
+
* @returns {string}
|
|
21
|
+
*/
|
|
22
|
+
function packageSearchHaystack (pkg) {
|
|
23
|
+
const parts = [
|
|
24
|
+
pkg.package,
|
|
25
|
+
pkg.slug,
|
|
26
|
+
slugToDisplayName(pkg.slug),
|
|
27
|
+
getPackagePitch(pkg),
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
for (const page of pkg.pages || []) {
|
|
31
|
+
if (page.id) {
|
|
32
|
+
parts.push(page.id, humanizeCapabilityId(page.id))
|
|
33
|
+
}
|
|
34
|
+
if (page.title) parts.push(page.title)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (const action of pkg.actions || []) {
|
|
38
|
+
if (action.id) {
|
|
39
|
+
parts.push(action.id, humanizeCapabilityId(action.id))
|
|
40
|
+
}
|
|
41
|
+
if (action.title) parts.push(action.title)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return parts.filter(Boolean).join(' ').toLowerCase()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {object[]} packages
|
|
49
|
+
* @param {string} query
|
|
50
|
+
* @returns {object[]}
|
|
51
|
+
*/
|
|
52
|
+
function filterPackages (packages, query) {
|
|
53
|
+
const normalized = query.trim().toLowerCase()
|
|
54
|
+
if (!normalized) return packages
|
|
55
|
+
return packages.filter((pkg) => packageSearchHaystack(pkg).includes(normalized))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Digital toolbox home — beliefs, how to invoke, showcase, then full catalog.
|
|
60
|
+
*/
|
|
61
|
+
export function PackagesBrowser () {
|
|
62
|
+
const app = useApp()
|
|
63
|
+
const router = useRouter()
|
|
64
|
+
const [query, setQuery] = useState('')
|
|
65
|
+
const packages = app?.manifestSummary?.packages || []
|
|
66
|
+
const showcaseActions = useMemo(
|
|
67
|
+
() => (app?.capabilities?.tools || []).slice(0, 6).map(tool => ({
|
|
68
|
+
id: tool.actionId,
|
|
69
|
+
title: tool.title,
|
|
70
|
+
description: tool.description,
|
|
71
|
+
mcpTool: tool.name,
|
|
72
|
+
access: tool.access,
|
|
73
|
+
})),
|
|
74
|
+
[app?.capabilities?.tools],
|
|
75
|
+
)
|
|
76
|
+
const sorted = useMemo(
|
|
77
|
+
() => [...packages].sort((a, b) =>
|
|
78
|
+
slugToDisplayName(a.slug).localeCompare(slugToDisplayName(b.slug)),
|
|
79
|
+
),
|
|
80
|
+
[packages],
|
|
81
|
+
)
|
|
82
|
+
const filtered = useMemo(
|
|
83
|
+
() => filterPackages(sorted, query),
|
|
84
|
+
[sorted, query],
|
|
85
|
+
)
|
|
86
|
+
const totalCount = sorted.length
|
|
87
|
+
const visibleCount = filtered.length
|
|
88
|
+
const isFiltering = query.trim().length > 0
|
|
89
|
+
|
|
90
|
+
const resourcesPkg = sorted.find((p) => p.slug === 'resources')
|
|
91
|
+
const storagePkg = sorted.find((p) => p.slug === 'storage')
|
|
92
|
+
const bookingPkg = sorted.find((p) => p.slug === 'booking')
|
|
93
|
+
const resourcesDetailHref = resourcesPkg
|
|
94
|
+
? router.getHref({ id: 'packages/detail', params: { packageSlug: 'resources' } })
|
|
95
|
+
: null
|
|
96
|
+
const storageDetailHref = storagePkg
|
|
97
|
+
? router.getHref({ id: 'packages/detail', params: { packageSlug: 'storage' } })
|
|
98
|
+
: null
|
|
99
|
+
const bookingDetailHref = bookingPkg
|
|
100
|
+
? router.getHref({ id: 'packages/detail', params: { packageSlug: 'booking' } })
|
|
101
|
+
: null
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<>
|
|
105
|
+
<CatalogPageStyles />
|
|
106
|
+
|
|
107
|
+
<Page maxWidth="xl" gap="l" className="pkg-store-root">
|
|
108
|
+
<PackageHero
|
|
109
|
+
title="package-catalog.home.hero.title"
|
|
110
|
+
text="package-catalog.home.hero.text"
|
|
111
|
+
meta="package-catalog.home.hero.meta"
|
|
112
|
+
metaParams={{ count: totalCount }}
|
|
113
|
+
maxWidth="l"
|
|
114
|
+
/>
|
|
115
|
+
|
|
116
|
+
<ContentHeader
|
|
117
|
+
inset="none"
|
|
118
|
+
title="package-catalog.browser.title"
|
|
119
|
+
description="package-catalog.browser.description"
|
|
120
|
+
/>
|
|
121
|
+
|
|
122
|
+
<View gap="m" roundness="m" inset="l" className="pkg-store-envelope">
|
|
123
|
+
<Text as="h2" variant="heading-tertiary" text="package-catalog.home.belief.title" />
|
|
124
|
+
<Text text="package-catalog.home.belief.body" className="pkg-store-prose--soft" />
|
|
125
|
+
</View>
|
|
126
|
+
|
|
127
|
+
<View gap="m" roundness="m" inset="l" className="pkg-store-envelope">
|
|
128
|
+
<Text as="h2" variant="heading-tertiary" text="package-catalog.home.howTo.title" />
|
|
129
|
+
<Text text="package-catalog.home.howTo.intro" className="pkg-store-prose--muted" />
|
|
130
|
+
|
|
131
|
+
<View gap="m" layout="row-wrap">
|
|
132
|
+
<HowToCard
|
|
133
|
+
icon="bolt"
|
|
134
|
+
title="package-catalog.home.howTo.sdk.title"
|
|
135
|
+
body={<SdkInvokeExample actionId="@ossy/resources/actions/list" />}
|
|
136
|
+
/>
|
|
137
|
+
<HowToCard
|
|
138
|
+
icon="link"
|
|
139
|
+
title="package-catalog.home.howTo.mcp.title"
|
|
140
|
+
body="package-catalog.home.howTo.mcp.body"
|
|
141
|
+
/>
|
|
142
|
+
<HowToCard
|
|
143
|
+
icon="key"
|
|
144
|
+
title="package-catalog.home.howTo.http.title"
|
|
145
|
+
body="package-catalog.home.howTo.http.body"
|
|
146
|
+
href="@profile/api-tokens/create"
|
|
147
|
+
/>
|
|
148
|
+
</View>
|
|
149
|
+
</View>
|
|
150
|
+
|
|
151
|
+
<View gap="m" roundness="m" inset="l" className="pkg-store-envelope">
|
|
152
|
+
<View layout="row" gap="s" alignItems="center">
|
|
153
|
+
<Icon name="software-upload" size="m" />
|
|
154
|
+
<Text as="h2" variant="heading-tertiary" text="package-catalog.home.showcase.title" />
|
|
155
|
+
</View>
|
|
156
|
+
<Text text="package-catalog.home.showcase.body" className="pkg-store-prose--muted" />
|
|
157
|
+
|
|
158
|
+
<View gap="m" layout="row-wrap">
|
|
159
|
+
{showcaseActions.map((action) => (
|
|
160
|
+
<ActionCapabilityCard key={action.id} action={action} />
|
|
161
|
+
))}
|
|
162
|
+
</View>
|
|
163
|
+
|
|
164
|
+
<View layout="row-wrap" gap="s">
|
|
165
|
+
{resourcesDetailHref && (
|
|
166
|
+
<Button
|
|
167
|
+
variant="secondary"
|
|
168
|
+
href={resourcesDetailHref}
|
|
169
|
+
label="package-catalog.home.showcase.viewResources"
|
|
170
|
+
/>
|
|
171
|
+
)}
|
|
172
|
+
{storageDetailHref && (
|
|
173
|
+
<Button
|
|
174
|
+
variant="secondary"
|
|
175
|
+
href={storageDetailHref}
|
|
176
|
+
label="package-catalog.home.showcase.viewStorage"
|
|
177
|
+
/>
|
|
178
|
+
)}
|
|
179
|
+
{bookingDetailHref && (
|
|
180
|
+
<Button
|
|
181
|
+
variant="link"
|
|
182
|
+
href={bookingDetailHref}
|
|
183
|
+
label="package-catalog.home.showcase.viewBooking"
|
|
184
|
+
/>
|
|
185
|
+
)}
|
|
186
|
+
</View>
|
|
187
|
+
</View>
|
|
188
|
+
|
|
189
|
+
{totalCount === 0 ? (
|
|
190
|
+
<Text text="package-catalog.emptyManifest" className="pkg-store-text-center pkg-store-text-muted" />
|
|
191
|
+
) : (
|
|
192
|
+
<View
|
|
193
|
+
roundness="m"
|
|
194
|
+
gap="m"
|
|
195
|
+
inset="l"
|
|
196
|
+
className="pkg-store-envelope"
|
|
197
|
+
>
|
|
198
|
+
<View layout="row" gap="s" alignItems="center">
|
|
199
|
+
<View justifyContent="center" alignItems="center">
|
|
200
|
+
<Icon name="package" size="m" />
|
|
201
|
+
</View>
|
|
202
|
+
<Text
|
|
203
|
+
variant="heading-tertiary"
|
|
204
|
+
as="h3"
|
|
205
|
+
text={isFiltering
|
|
206
|
+
? 'package-catalog.section.matching'
|
|
207
|
+
: 'package-catalog.section.all'}
|
|
208
|
+
/>
|
|
209
|
+
</View>
|
|
210
|
+
|
|
211
|
+
<Text text="package-catalog.section.description" />
|
|
212
|
+
|
|
213
|
+
<Text
|
|
214
|
+
variant="small"
|
|
215
|
+
className="pkg-store-grid-meta"
|
|
216
|
+
text="package-catalog.gridMeta"
|
|
217
|
+
params={{ visible: visibleCount, total: totalCount }}
|
|
218
|
+
/>
|
|
219
|
+
|
|
220
|
+
<View className="pkg-store-search pkg-store-search--inline">
|
|
221
|
+
<Input
|
|
222
|
+
type="search"
|
|
223
|
+
value={query}
|
|
224
|
+
onChange={(event) => setQuery(event.target.value)}
|
|
225
|
+
placeholder="package-catalog.search.placeholder"
|
|
226
|
+
aria-label="package-catalog.search.label"
|
|
227
|
+
autoComplete="off"
|
|
228
|
+
/>
|
|
229
|
+
</View>
|
|
230
|
+
|
|
231
|
+
{visibleCount === 0 ? (
|
|
232
|
+
<View gap="s" alignItems="center" inset="m">
|
|
233
|
+
<Text text="package-catalog.search.noResults.title" className="pkg-store-text-strong" />
|
|
234
|
+
<Text
|
|
235
|
+
variant="small"
|
|
236
|
+
text="package-catalog.search.noResults.hint"
|
|
237
|
+
className="pkg-store-text-muted"
|
|
238
|
+
/>
|
|
239
|
+
</View>
|
|
240
|
+
) : (
|
|
241
|
+
<View gap="m" layout="row-wrap">
|
|
242
|
+
{filtered.map((pkg) => (
|
|
243
|
+
<PackageStoreCard key={pkg.package} pkg={pkg} router={router} />
|
|
244
|
+
))}
|
|
245
|
+
</View>
|
|
246
|
+
)}
|
|
247
|
+
</View>
|
|
248
|
+
)}
|
|
249
|
+
</Page>
|
|
250
|
+
</>
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* @param {{ pkg: object, router: ReturnType<typeof useRouter> }} props
|
|
256
|
+
*/
|
|
257
|
+
function PackageStoreCard ({ pkg, router }) {
|
|
258
|
+
const displayName = slugToDisplayName(pkg.slug)
|
|
259
|
+
const pitch = getPackagePitch(pkg)
|
|
260
|
+
const icon = getPackageIcon(pkg)
|
|
261
|
+
const actionCount = pkg.actions?.length ?? 0
|
|
262
|
+
const detailHref = router.getHref({
|
|
263
|
+
id: 'packages/detail',
|
|
264
|
+
params: { packageSlug: pkg.slug },
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
return (
|
|
268
|
+
<View
|
|
269
|
+
as="a"
|
|
270
|
+
href={detailHref}
|
|
271
|
+
roundness="m"
|
|
272
|
+
surface="primary"
|
|
273
|
+
inset="m"
|
|
274
|
+
gap="m"
|
|
275
|
+
selectable
|
|
276
|
+
className="pkg-store-package-link"
|
|
277
|
+
>
|
|
278
|
+
<View justifyContent="center" alignItems="center">
|
|
279
|
+
<Icon name={icon} size="m" />
|
|
280
|
+
</View>
|
|
281
|
+
<Text variant="small" className="pkg-store-text-bold pkg-store-text-center">
|
|
282
|
+
{displayName}
|
|
283
|
+
</Text>
|
|
284
|
+
<Text variant="small" className="pkg-store-text-center">
|
|
285
|
+
{pitch}
|
|
286
|
+
</Text>
|
|
287
|
+
{actionCount > 0 && (
|
|
288
|
+
<Text variant="small" className="pkg-store-text-center pkg-store-text-subtle">
|
|
289
|
+
{actionCount} action{actionCount === 1 ? '' : 's'}
|
|
290
|
+
</Text>
|
|
291
|
+
)}
|
|
292
|
+
</View>
|
|
293
|
+
)
|
|
294
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Icon, Text, View } from '@ossy/design-system'
|
|
3
|
+
import { humanizeCapabilityId } from './packageCopy.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {{ component: { id: string, title?: string }, icon?: string }} props
|
|
7
|
+
*/
|
|
8
|
+
export function WidgetCapabilityCard ({ component, icon = 'layout-grid' }) {
|
|
9
|
+
const title = component.title || humanizeCapabilityId(component.id)
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<View
|
|
13
|
+
gap="s"
|
|
14
|
+
inset="m"
|
|
15
|
+
roundness="m"
|
|
16
|
+
surface="primary"
|
|
17
|
+
className="pkg-store-widget-card"
|
|
18
|
+
>
|
|
19
|
+
<View justifyContent="center" alignItems="center">
|
|
20
|
+
<Icon name={icon} size="m" />
|
|
21
|
+
</View>
|
|
22
|
+
<Text className="pkg-store-text-strong pkg-store-text-center">{title}</Text>
|
|
23
|
+
<Text className="pkg-store-mono pkg-store-mono--sm pkg-store-text-subtle pkg-store-text-center">
|
|
24
|
+
{component.id}
|
|
25
|
+
</Text>
|
|
26
|
+
</View>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"package-catalog.home.hero.title": "A digital toolbox for your business",
|
|
3
|
+
"package-catalog.browser.title": "Capabilities",
|
|
4
|
+
"package-catalog.browser.description": "Browse documented, invokable capabilities for your workspace.",
|
|
5
|
+
"package-catalog.home.hero.text": "Ossy ships documented, invokable capabilities — booking, auth, resources, and more. Every feature is callable from the SDK, API, or MCP. UI is a projection, not the product.",
|
|
6
|
+
"package-catalog.home.hero.meta": "{count} capabilities · ready to use in apps and agents",
|
|
7
|
+
"package-catalog.home.belief.title": "What we believe",
|
|
8
|
+
"package-catalog.home.belief.body": "People want a new way to interact with technology. AI is becoming the primary layer — agents should run your business operations, not just answer questions. We build compact, composable tools that plug into MCP-compatible hosts — Cursor, Claude Desktop, GitHub Copilot in VS Code, and other MCP agents — as well as your own apps and the web.",
|
|
9
|
+
"package-catalog.home.howTo.title": "How to use capabilities",
|
|
10
|
+
"package-catalog.home.howTo.intro": "Three paths to the same actions. Pick what fits your environment.",
|
|
11
|
+
"package-catalog.home.howTo.sdk.title": "SDK",
|
|
12
|
+
"package-catalog.home.howTo.sdk.body": "sdk.invoke('@ossy/booking/actions/create', payload) from Node or React apps.",
|
|
13
|
+
"package-catalog.home.howTo.mcp.title": "MCP",
|
|
14
|
+
"package-catalog.home.howTo.mcp.body": "Connect Cursor to the app MCP endpoint at /mcp. Agents call ossy_* tools with your API token and workspace id headers.",
|
|
15
|
+
"package-catalog.home.howTo.http.title": "HTTP",
|
|
16
|
+
"package-catalog.home.howTo.http.body": "Call actions over HTTP with Bearer token authentication — for scripts, agents, and integrations outside the SDK.",
|
|
17
|
+
"package-catalog.home.howTo.http.createLink": "Create bearer token →",
|
|
18
|
+
"package-catalog.home.showcase.title": "Featured capabilities",
|
|
19
|
+
"package-catalog.home.showcase.body": "Data layout actions first — resources and storage — plus booking as a secondary showcase. Same contract in SDK, HTTP, and MCP.",
|
|
20
|
+
"package-catalog.home.showcase.viewResources": "Resources capability docs →",
|
|
21
|
+
"package-catalog.home.showcase.viewStorage": "Storage capability docs →",
|
|
22
|
+
"package-catalog.home.showcase.viewBooking": "Booking capability docs →",
|
|
23
|
+
"packages.documentTitle": "Capabilities",
|
|
24
|
+
"package-catalog.hero.title": "Ossy platform capabilities",
|
|
25
|
+
"package-catalog.hero.text": "Discover ready-made capabilities for your workspace — pages, server actions, APIs, components, and integrations you can use in apps and agents.",
|
|
26
|
+
"package-catalog.hero.meta": "{count} capabilities · browse and explore",
|
|
27
|
+
"package-catalog.search.label": "Search capabilities",
|
|
28
|
+
"package-catalog.search.placeholder": "Search capabilities, descriptions, pages, actions…",
|
|
29
|
+
"package-catalog.search.noResults.title": "No capabilities match your search",
|
|
30
|
+
"package-catalog.search.noResults.hint": "Try a capability name, slug, description, or action id — e.g. \"booking\" or \"sign-in\".",
|
|
31
|
+
"package-catalog.section.all": "All capabilities",
|
|
32
|
+
"package-catalog.section.matching": "Matching capabilities",
|
|
33
|
+
"package-catalog.section.description": "Each card opens showcase and documentation — actions, widgets, and optional pages.",
|
|
34
|
+
"package-catalog.gridMeta": "Showing {visible} of {total} capabilities",
|
|
35
|
+
"package-catalog.emptyManifest": "Capability catalog unavailable. Rebuild the app after upgrading @ossy/platform.",
|
|
36
|
+
"package-catalog.detail.notFound.title": "Capability not found",
|
|
37
|
+
"package-catalog.detail.notFound.body": "No manifest entries for slug: {slug}",
|
|
38
|
+
"package-catalog.detail.backToPackages": "Back to capabilities",
|
|
39
|
+
"package-catalog.detail.backAll": "← All capabilities",
|
|
40
|
+
"package-catalog.detail.atAGlance": "At a glance",
|
|
41
|
+
"package-catalog.detail.section.widgets.title": "Widgets",
|
|
42
|
+
"package-catalog.detail.section.widgets.subtitle": "Compact widgets for your workspace dashboard — UI components you can also embed in pages or compose in apps.",
|
|
43
|
+
"package-catalog.detail.section.pages.title": "Pages",
|
|
44
|
+
"package-catalog.detail.section.pages.subtitle": "Routes and screens included with this capability — open any page to see it live.",
|
|
45
|
+
"package-catalog.detail.section.actions.title": "Actions",
|
|
46
|
+
"package-catalog.detail.section.actions.subtitle": "Invokable intents — call directly via SDK, POST /actions, or MCP tools. This is the agent-facing surface, not background jobs.",
|
|
47
|
+
"package-catalog.detail.section.resources.title": "Resource types",
|
|
48
|
+
"package-catalog.detail.section.resources.subtitle": "Structured entities and templates registered for resources.",
|
|
49
|
+
"package-catalog.detail.section.apis.title": "APIs",
|
|
50
|
+
"package-catalog.detail.section.apis.subtitle": "HTTP endpoints exposed by this capability.",
|
|
51
|
+
"package-catalog.detail.section.components.title": "UI components",
|
|
52
|
+
"package-catalog.detail.section.components.subtitle": "Reusable React components registered in the manifest.",
|
|
53
|
+
"package-catalog.detail.section.tasks.title": "Background tasks",
|
|
54
|
+
"package-catalog.detail.section.tasks.subtitle": "Background and scheduled handlers — triggered by events or cron, not called via SDK, POST /actions, or MCP. Ids use the tasks/ path (distinct from actions/).",
|
|
55
|
+
"package-catalog.detail.section.integrations.title": "Integrations",
|
|
56
|
+
"package-catalog.detail.section.integrations.subtitle": "Third-party connectors and credential requirements.",
|
|
57
|
+
"package-catalog.detail.section.emails.title": "Email templates",
|
|
58
|
+
"package-catalog.detail.section.emails.subtitle": "Transactional messages sent by this capability.",
|
|
59
|
+
"package-catalog.detail.section.aggregates.title": "Aggregates",
|
|
60
|
+
"package-catalog.detail.section.aggregates.subtitle": "Domain aggregates and bounded-context entities.",
|
|
61
|
+
"package-catalog.detail.howTo.title": "How to use this capability",
|
|
62
|
+
"package-catalog.detail.howTo.intro": "Ways to interact with this capability — pick a path to jump to the relevant section below.",
|
|
63
|
+
"package-catalog.detail.howTo.sectionLink": "View section ↓",
|
|
64
|
+
"package-catalog.detail.howTo.sdk.body": "Call actions from Node or React apps using the Ossy SDK — typed payloads and workspace context built in.",
|
|
65
|
+
"package-catalog.detail.howTo.http.body": "Call actions over HTTP with Bearer token authentication — create a token in Profile for scripts and automation.",
|
|
66
|
+
"package-catalog.detail.howTo.mcp.body": "Connect Cursor to http://localhost:3006/mcp when MCP tools are available for {slug}.",
|
|
67
|
+
"package-catalog.detail.howTo.mcp.booking.body": "Connect Cursor to /mcp on the running app so agents can call ossy_booking_* tools with your API token.",
|
|
68
|
+
"package-catalog.detail.howTo.widgets.title": "UI widgets",
|
|
69
|
+
"package-catalog.detail.howTo.widgets.body": "Embed dashboard widgets or compose UI from packaged components in pages and apps.",
|
|
70
|
+
"package-catalog.detail.howTo.tasks.title": "Background tasks",
|
|
71
|
+
"package-catalog.detail.howTo.tasks.body": "Event-driven and scheduled handlers run in the background — triggered by the platform, not invoked like actions.",
|
|
72
|
+
"package-catalog.detail.howTo.aggregates.title": "Aggregates",
|
|
73
|
+
"package-catalog.detail.howTo.aggregates.body": "Domain entities and bounded-context models exposed by this capability for reads and composition.",
|
|
74
|
+
"package-catalog.detail.footer.title": "See it in action",
|
|
75
|
+
"package-catalog.detail.footer.body": "Open {pageTitle} to experience {packageName} in a live workspace.",
|
|
76
|
+
"package-catalog.detail.footer.openPage": "Open {pageTitle}",
|
|
77
|
+
"package-catalog.detail.openPage": "Open page",
|
|
78
|
+
"package-catalog.detail.service.label": "Workspace service",
|
|
79
|
+
"package-catalog.detail.service.on": "enabled",
|
|
80
|
+
"package-catalog.detail.service.off": "disabled",
|
|
81
|
+
"package-catalog.detail.service.enable": "Enable service",
|
|
82
|
+
"package-catalog.detail.service.disable": "Disable service",
|
|
83
|
+
"package-catalog.detail.meta.capabilities": "{count} capabilit{countSuffix} included",
|
|
84
|
+
"package-catalog.detail.openAPage": "Open a page"
|
|
85
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {string | Record<string, string> | undefined} path
|
|
3
|
+
* @returns {string}
|
|
4
|
+
*/
|
|
5
|
+
export function formatPagePaths (path) {
|
|
6
|
+
if (!path) return '—'
|
|
7
|
+
if (typeof path === 'string') return path
|
|
8
|
+
return Object.entries(path)
|
|
9
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
10
|
+
.map(([lang, p]) => `${lang}: ${p}`)
|
|
11
|
+
.join(' · ')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Localized page path without the `/:language` router prefix.
|
|
16
|
+
*
|
|
17
|
+
* @param {string | Record<string, string> | undefined} path
|
|
18
|
+
* @param {string | undefined} language
|
|
19
|
+
* @returns {string}
|
|
20
|
+
*/
|
|
21
|
+
export function formatPagePathForLanguage (path, language) {
|
|
22
|
+
if (!path) return '—'
|
|
23
|
+
if (typeof path === 'string') return stripLanguagePrefixFromPath(path, language)
|
|
24
|
+
const lang = language && path[language] ? language : Object.keys(path).sort()[0]
|
|
25
|
+
const segment = path[lang]
|
|
26
|
+
return segment ? stripLanguagePrefixFromPath(segment, lang) : '—'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {string} path
|
|
31
|
+
* @param {string | undefined} language
|
|
32
|
+
* @returns {string}
|
|
33
|
+
*/
|
|
34
|
+
function stripLanguagePrefixFromPath (path, language) {
|
|
35
|
+
if (!path) return '—'
|
|
36
|
+
if (language) {
|
|
37
|
+
const prefix = `/${language}`
|
|
38
|
+
if (path === prefix) return '/'
|
|
39
|
+
if (path.startsWith(`${prefix}/`)) return path.slice(prefix.length)
|
|
40
|
+
}
|
|
41
|
+
return path
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const monoStyle = {
|
|
45
|
+
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
|
|
46
|
+
fontSize: '0.8125rem',
|
|
47
|
+
lineHeight: 1.4,
|
|
48
|
+
wordBreak: 'break-all',
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export { monoStyle }
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {object} pkg
|
|
55
|
+
* @returns {number}
|
|
56
|
+
*/
|
|
57
|
+
export function countPackageCapabilities (pkg) {
|
|
58
|
+
return (
|
|
59
|
+
(pkg.pages?.length || 0) +
|
|
60
|
+
(pkg.apis?.length || 0) +
|
|
61
|
+
(pkg.actions?.length || 0) +
|
|
62
|
+
(pkg.components?.length || 0) +
|
|
63
|
+
(pkg.schemas?.length || 0) +
|
|
64
|
+
(pkg.tasks?.length || 0) +
|
|
65
|
+
(pkg.integrations?.length || 0) +
|
|
66
|
+
(pkg.emails?.length || 0) +
|
|
67
|
+
(pkg.aggregates?.length || 0)
|
|
68
|
+
)
|
|
69
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { Definition } from './Definition.js'
|
|
2
|
+
export { PackagesBrowser } from './PackagesBrowser.jsx'
|
|
3
|
+
export { CatalogPageStyles } from './CatalogPageStyles.jsx'
|
|
4
|
+
export {
|
|
5
|
+
slugToDisplayName,
|
|
6
|
+
getPackagePitch,
|
|
7
|
+
getPackageIcon,
|
|
8
|
+
humanizeCapabilityId,
|
|
9
|
+
} from './packageCopy.js'
|