@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.
@@ -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'