@inferencesh/ui 0.0.0-stage → 0.1.2

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 (165) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +68 -2
  3. package/components.json +22 -0
  4. package/package.json +231 -4
  5. package/src/components/audio-player.tsx +549 -0
  6. package/src/components/button.tsx +13 -0
  7. package/src/components/chart.tsx +403 -0
  8. package/src/components/circuit.tsx +276 -0
  9. package/src/components/code-block/code-block.tsx +260 -0
  10. package/src/components/code-block/index.ts +18 -0
  11. package/src/components/code-block/languages.ts +311 -0
  12. package/src/components/code-block/styles.ts +39 -0
  13. package/src/components/code-block/tokenizer.ts +290 -0
  14. package/src/components/code-block/types.ts +38 -0
  15. package/src/components/code-block/utils.ts +39 -0
  16. package/src/components/code.tsx +48 -0
  17. package/src/components/cookie-consent.tsx +137 -0
  18. package/src/components/copy-button.tsx +44 -0
  19. package/src/components/country-dropdown.tsx +188 -0
  20. package/src/components/glow-loader-fast.tsx +32 -0
  21. package/src/components/glow-loader-mono.tsx +33 -0
  22. package/src/components/glow-loader-sm-fast.tsx +32 -0
  23. package/src/components/glow-loader-sm-mono.tsx +34 -0
  24. package/src/components/glow-loader-sm.tsx +32 -0
  25. package/src/components/glow-loader.tsx +32 -0
  26. package/src/components/glow-logo.tsx +32 -0
  27. package/src/components/gradient.tsx +80 -0
  28. package/src/components/index.ts +45 -0
  29. package/src/components/inline-sidebar.tsx +351 -0
  30. package/src/components/input-color.tsx +349 -0
  31. package/src/components/logo-base.tsx +213 -0
  32. package/src/components/logo-color.tsx +23 -0
  33. package/src/components/logo-full.tsx +71 -0
  34. package/src/components/logo-svg.tsx +23 -0
  35. package/src/components/menu-navigation.tsx +359 -0
  36. package/src/components/multi-select.tsx +721 -0
  37. package/src/components/pastel-gradient.tsx +25 -0
  38. package/src/components/pixel-symmetry.tsx +151 -0
  39. package/src/components/progress.tsx +28 -0
  40. package/src/components/scroll-more.tsx +131 -0
  41. package/src/components/select.tsx +85 -0
  42. package/src/components/shimmering-text.tsx +108 -0
  43. package/src/components/sidebar-light.tsx +102 -0
  44. package/src/components/sonner.tsx +38 -0
  45. package/src/components/theme-switcher-sidebar.tsx +56 -0
  46. package/src/components/theme-switcher-simple.tsx +110 -0
  47. package/src/components/theme-switcher.tsx +47 -0
  48. package/src/components/toggle-group.tsx +55 -0
  49. package/src/components/tooltip-button.tsx +91 -0
  50. package/src/components/tooltip.tsx +71 -0
  51. package/src/components/tree-view.tsx +499 -0
  52. package/src/components/unglow-loader.tsx +33 -0
  53. package/src/components/waveform.tsx +1632 -0
  54. package/src/components/zoomable-image.tsx +76 -0
  55. package/src/docs/api-docs.tsx +763 -0
  56. package/src/docs/callout.tsx +160 -0
  57. package/src/docs/code-group.tsx +215 -0
  58. package/src/docs/copy-menu.tsx +143 -0
  59. package/src/docs/doc-tabs.tsx +84 -0
  60. package/src/docs/index.ts +16 -0
  61. package/src/docs/steps.tsx +105 -0
  62. package/src/docs/table-of-contents.tsx +174 -0
  63. package/src/examples/guide-markdown.ts +129 -0
  64. package/src/examples/index.ts +1153 -0
  65. package/src/global.d.ts +1 -0
  66. package/src/hooks/use-copy-to-clipboard.ts +36 -0
  67. package/src/hooks/use-mobile.ts +19 -0
  68. package/src/lib/display-name.ts +15 -0
  69. package/src/lib/example-input.ts +130 -0
  70. package/src/lib/plans.ts +44 -0
  71. package/src/lib/safe-content.ts +137 -0
  72. package/src/lib/schema.ts +320 -0
  73. package/src/lib/storage.ts +108 -0
  74. package/src/markdown/docs-markdown-renderer.tsx +518 -0
  75. package/src/markdown/helpers.ts +95 -0
  76. package/src/markdown/index.ts +24 -0
  77. package/src/markdown/markdown-renderer.tsx +232 -0
  78. package/src/markdown/youtube-embed.tsx +43 -0
  79. package/src/og/image.ts +312 -0
  80. package/src/og/index.tsx +430 -0
  81. package/src/og/query.ts +26 -0
  82. package/src/shadcn/accordion.tsx +71 -0
  83. package/src/shadcn/alert-dialog.tsx +187 -0
  84. package/src/shadcn/alert.tsx +75 -0
  85. package/src/shadcn/aspect-ratio.tsx +22 -0
  86. package/src/shadcn/attachment.tsx +207 -0
  87. package/src/shadcn/avatar.tsx +106 -0
  88. package/src/shadcn/badge.tsx +51 -0
  89. package/src/shadcn/breadcrumb.tsx +124 -0
  90. package/src/shadcn/bubble.tsx +127 -0
  91. package/src/shadcn/button-group.tsx +87 -0
  92. package/src/shadcn/button.tsx +57 -0
  93. package/src/shadcn/calendar.tsx +219 -0
  94. package/src/shadcn/card.tsx +102 -0
  95. package/src/shadcn/carousel.tsx +242 -0
  96. package/src/shadcn/chart.tsx +372 -0
  97. package/src/shadcn/checkbox.tsx +28 -0
  98. package/src/shadcn/collapsible.tsx +19 -0
  99. package/src/shadcn/combobox.tsx +297 -0
  100. package/src/shadcn/command.tsx +194 -0
  101. package/src/shadcn/context-menu.tsx +270 -0
  102. package/src/shadcn/dialog.tsx +158 -0
  103. package/src/shadcn/direction.tsx +4 -0
  104. package/src/shadcn/drawer.tsx +227 -0
  105. package/src/shadcn/dropdown-menu.tsx +265 -0
  106. package/src/shadcn/empty.tsx +103 -0
  107. package/src/shadcn/field.tsx +238 -0
  108. package/src/shadcn/form.tsx +173 -0
  109. package/src/shadcn/hover-card.tsx +50 -0
  110. package/src/shadcn/index.ts +53 -0
  111. package/src/shadcn/input-group.tsx +156 -0
  112. package/src/shadcn/input-otp.tsx +84 -0
  113. package/src/shadcn/input.tsx +19 -0
  114. package/src/shadcn/item.tsx +201 -0
  115. package/src/shadcn/kbd.tsx +26 -0
  116. package/src/shadcn/label.tsx +19 -0
  117. package/src/shadcn/marker.tsx +70 -0
  118. package/src/shadcn/menubar.tsx +278 -0
  119. package/src/shadcn/message-scroller.tsx +131 -0
  120. package/src/shadcn/message.tsx +91 -0
  121. package/src/shadcn/native-select.tsx +60 -0
  122. package/src/shadcn/navigation-menu.tsx +167 -0
  123. package/src/shadcn/pagination.tsx +130 -0
  124. package/src/shadcn/popover.tsx +87 -0
  125. package/src/shadcn/progress.tsx +82 -0
  126. package/src/shadcn/radio-group.tsx +35 -0
  127. package/src/shadcn/resizable.tsx +49 -0
  128. package/src/shadcn/scroll-area.tsx +52 -0
  129. package/src/shadcn/select.tsx +200 -0
  130. package/src/shadcn/separator.tsx +22 -0
  131. package/src/shadcn/sheet.tsx +136 -0
  132. package/src/shadcn/sidebar.tsx +721 -0
  133. package/src/shadcn/skeleton.tsx +13 -0
  134. package/src/shadcn/slider.tsx +51 -0
  135. package/src/shadcn/sonner.tsx +49 -0
  136. package/src/shadcn/spinner.tsx +10 -0
  137. package/src/shadcn/switch.tsx +29 -0
  138. package/src/shadcn/table.tsx +115 -0
  139. package/src/shadcn/tabs.tsx +79 -0
  140. package/src/shadcn/textarea.tsx +17 -0
  141. package/src/shadcn/toggle-group.tsx +89 -0
  142. package/src/shadcn/toggle.tsx +44 -0
  143. package/src/shadcn/tooltip.tsx +63 -0
  144. package/src/theme/config.ts +48 -0
  145. package/src/theme/css/base.css +92 -0
  146. package/src/theme/css/bitboy.css +75 -0
  147. package/src/theme/css/dark.css +47 -0
  148. package/src/theme/css/designer.css +122 -0
  149. package/src/theme/css/fonts.css +33 -0
  150. package/src/theme/css/globals.css +14 -0
  151. package/src/theme/css/hackintosh.css +57 -0
  152. package/src/theme/css/llama.css +103 -0
  153. package/src/theme/css/safelist.css +39 -0
  154. package/src/theme/css/shadcn.css +77 -0
  155. package/src/theme/css/theme.css +216 -0
  156. package/src/theme/embed-theme.ts +146 -0
  157. package/src/theme/fonts/FiraCode-VF.woff2 +0 -0
  158. package/src/theme/fonts/Hack-Regular.woff2 +0 -0
  159. package/src/theme/fonts/LICENSE-FiraCode.txt +93 -0
  160. package/src/theme/fonts/LICENSE-Hack.txt +45 -0
  161. package/src/theme/fonts/LICENSE-MonaSans.txt +93 -0
  162. package/src/theme/fonts/Mona-Sans.woff2 +0 -0
  163. package/src/theme/index.ts +15 -0
  164. package/src/utils/cn.ts +3 -0
  165. package/src/utils/index.ts +2 -0
@@ -0,0 +1,105 @@
1
+ 'use client'
2
+
3
+ import { cn } from '../utils'
4
+ import { Check, Circle } from 'lucide-react'
5
+ import React, { useMemo } from 'react'
6
+
7
+ // =============================================================================
8
+ // Steps Component
9
+ // =============================================================================
10
+
11
+ interface StepInfo {
12
+ title: string
13
+ children: React.ReactNode
14
+ icon?: React.ReactNode
15
+ }
16
+
17
+ function extractSteps(children: React.ReactNode): StepInfo[] {
18
+ const steps: StepInfo[] = []
19
+
20
+ React.Children.forEach(children, (child) => {
21
+ if (!React.isValidElement(child)) return
22
+
23
+ const type = child.type
24
+ const typeName = typeof type === 'string' ? type.toLowerCase() : ''
25
+
26
+ if (typeName === 'step' || (typeof type === 'function' && (type as React.FC).displayName === 'DocsStep')) {
27
+ const props = child.props as { title?: string; icon?: React.ReactNode; children?: React.ReactNode }
28
+ steps.push({
29
+ title: props.title || '',
30
+ icon: props.icon,
31
+ children: props.children,
32
+ })
33
+ }
34
+ })
35
+
36
+ return steps
37
+ }
38
+
39
+ export interface StepsProps {
40
+ children: React.ReactNode
41
+ className?: string
42
+ /** Title text size: 'p' (default), 'h2', 'h3' */
43
+ titleSize?: 'p' | 'h2' | 'h3'
44
+ }
45
+
46
+ export function Steps({ children, className, titleSize = 'p' }: StepsProps) {
47
+ const steps = useMemo(() => extractSteps(children), [children])
48
+
49
+ if (steps.length === 0) {
50
+ return <div className={className}><span>{children}</span></div>
51
+ }
52
+
53
+ const titleClasses = {
54
+ 'p': 'text-base font-semibold',
55
+ 'h2': 'text-xl font-bold',
56
+ 'h3': 'text-lg font-semibold',
57
+ }
58
+
59
+ return (
60
+ <div className={cn('my-6 space-y-0', className)}>
61
+ {steps.map((step, i) => (
62
+ <div key={i} className="relative flex gap-4 pb-8 last:pb-0">
63
+ {/* Vertical line */}
64
+ {i < steps.length - 1 && (
65
+ <div className="absolute left-[15px] top-8 h-[calc(100%-16px)] w-px bg-border" />
66
+ )}
67
+
68
+ {/* Step number indicator */}
69
+ <div className="relative flex h-8 w-8 shrink-0 items-center justify-center rounded-full border-2 border-brand-pink bg-background text-sm font-semibold text-brand-pink">
70
+ {step.icon || i + 1}
71
+ </div>
72
+
73
+ {/* Content */}
74
+ <div className="flex-1 pt-0.5">
75
+ {step.title && (
76
+ <div className={cn(titleClasses[titleSize], 'mb-2')}>
77
+ {step.title}
78
+ </div>
79
+ )}
80
+ <div className="text-sm text-muted-foreground prose prose-sm dark:prose-invert max-w-none">
81
+ {step.children}
82
+ </div>
83
+ </div>
84
+ </div>
85
+ ))}
86
+ </div>
87
+ )
88
+ }
89
+
90
+ // =============================================================================
91
+ // Step Component (individual step)
92
+ // =============================================================================
93
+
94
+ export interface StepProps {
95
+ title?: string
96
+ icon?: React.ReactNode
97
+ children: React.ReactNode
98
+ }
99
+
100
+ export function Step({ children }: StepProps) {
101
+ // This component is just a marker - actual rendering happens in Steps
102
+ return <>{children}</>
103
+ }
104
+
105
+ Step.displayName = 'DocsStep'
@@ -0,0 +1,174 @@
1
+ "use client"
2
+
3
+ import { cn } from '../utils'
4
+ import { useEffect, useMemo, useState } from 'react'
5
+ import { TableOfContentsIcon } from 'lucide-react'
6
+
7
+ export interface TocItem {
8
+ id: string
9
+ title: string
10
+ level?: number
11
+ children?: TocItem[]
12
+ }
13
+
14
+ interface TableOfContentsProps {
15
+ /** Markdown content to extract headings from */
16
+ content?: string
17
+ /** Pre-defined items (alternative to content extraction) */
18
+ items?: TocItem[]
19
+ /** Optional header slot (e.g., language selector) */
20
+ header?: React.ReactNode
21
+ /** Optional footer slot (e.g., link to full docs) */
22
+ footer?: React.ReactNode
23
+ className?: string
24
+ }
25
+
26
+ // Extract headings from markdown content (h2 and h3 only, h1 is page title)
27
+ // Nests h3s under their preceding h2
28
+ function extractHeadings(content: string): TocItem[] {
29
+ const result: TocItem[] = []
30
+ const lines = content.split('\n')
31
+ let currentH2: TocItem | null = null
32
+
33
+ for (const line of lines) {
34
+ const match = line.match(/^(#{2,3})\s+(.+)$/)
35
+ if (match) {
36
+ const level = match[1].length
37
+ const title = match[2].trim()
38
+ // Create a URL-safe ID from the heading text
39
+ const id = title
40
+ .toLowerCase()
41
+ .replace(/[^a-z0-9]+/g, '-')
42
+ .replace(/(^-|-$)/g, '')
43
+
44
+ if (level === 2) {
45
+ currentH2 = { id, title, level, children: [] }
46
+ result.push(currentH2)
47
+ } else if (level === 3) {
48
+ const item = { id, title, level }
49
+ if (currentH2) {
50
+ currentH2.children = currentH2.children || []
51
+ currentH2.children.push(item)
52
+ } else {
53
+ // h3 without preceding h2, add to root
54
+ result.push(item)
55
+ }
56
+ }
57
+ }
58
+ }
59
+
60
+ return result
61
+ }
62
+
63
+ // Flatten nested items for intersection observer
64
+ function flattenItems(items: TocItem[]): TocItem[] {
65
+ return items.flatMap(item => [item, ...(item.children || [])])
66
+ }
67
+
68
+ export function TableOfContents({ content, items, header, footer, className }: TableOfContentsProps) {
69
+ const [activeId, setActiveId] = useState<string>('')
70
+
71
+ // Use provided items or extract from markdown
72
+ const tocItems = useMemo(() => {
73
+ if (items) return items
74
+ if (content) return extractHeadings(content)
75
+ return []
76
+ }, [items, content])
77
+
78
+ // Flatten for observer
79
+ const allItems = useMemo(() => flattenItems(tocItems), [tocItems])
80
+
81
+ useEffect(() => {
82
+ const observer = new IntersectionObserver(
83
+ (entries) => {
84
+ entries.forEach((entry) => {
85
+ if (entry.isIntersecting) {
86
+ setActiveId(entry.target.id)
87
+ }
88
+ })
89
+ },
90
+ {
91
+ rootMargin: '-80px 0px -80% 0px',
92
+ threshold: 0,
93
+ }
94
+ )
95
+
96
+ // Observe all heading elements
97
+ allItems.forEach(({ id }) => {
98
+ const element = document.getElementById(id)
99
+ if (element) {
100
+ observer.observe(element)
101
+ }
102
+ })
103
+
104
+ return () => observer.disconnect()
105
+ }, [allItems])
106
+
107
+ if (tocItems.length === 0) {
108
+ return null
109
+ }
110
+
111
+ const renderItem = (item: TocItem, isChild = false) => {
112
+ const isActive = activeId === item.id
113
+ const level = item.level || (isChild ? 3 : 2)
114
+
115
+ return (
116
+ <li key={item.id}>
117
+ <a
118
+ href={`#${item.id}`}
119
+ onClick={(e) => {
120
+ e.preventDefault()
121
+ const element = document.getElementById(item.id)
122
+ if (element) {
123
+ element.scrollIntoView({ behavior: 'smooth' })
124
+ setActiveId(item.id)
125
+ }
126
+ }}
127
+ className={cn(
128
+ 'flex items-center text-[13px] py-1 -ml-px border-l transition-colors lowercase',
129
+ !isChild && 'pl-3 font-medium',
130
+ isChild && 'pl-4 text-xs',
131
+ isActive
132
+ ? 'border-brand-pink text-brand-pink'
133
+ : 'border-transparent text-muted-foreground hover:text-foreground hover:border-muted-foreground'
134
+ )}
135
+ >
136
+ {isChild && (
137
+ <span className={cn(
138
+ "mr-1.5 text-[10px] font-mono",
139
+ isActive ? "text-brand-pink" : "text-muted-foreground/50"
140
+ )}>
141
+ └
142
+ </span>
143
+ )}
144
+ {item.title}
145
+ </a>
146
+ {item.children && item.children.length > 0 && (
147
+ <ul className="space-y-0.5">
148
+ {item.children.map(child => renderItem(child, true))}
149
+ </ul>
150
+ )}
151
+ </li>
152
+ )
153
+ }
154
+
155
+ return (
156
+ <nav className={cn('relative', className)}>
157
+ {header}
158
+
159
+ <div className="flex items-center gap-2 mb-3">
160
+ <TableOfContentsIcon className="h-4 w-4 text-muted-foreground" />
161
+ <p className="text-xs font-bold text-muted-foreground">
162
+ on this page
163
+ </p>
164
+ </div>
165
+
166
+ <ul className="space-y-1 border-l border-border">
167
+ {tocItems.map(item => renderItem(item))}
168
+ </ul>
169
+
170
+ {footer}
171
+ </nav>
172
+ )
173
+ }
174
+
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Generate a full API guide as markdown for an app.
3
+ * Used by CopyMenu on both app.inference.sh and inference shell SEO pages.
4
+ */
5
+
6
+ import { apiKeySetup, generateBackgroundExamples, generateFileExamples, generateResultOnlyExamples, generateStreamingExamples, installCommands } from '.'
7
+ import { exampleInput } from '../lib/example-input'
8
+ import { getFieldsFromSchema } from '../lib/schema'
9
+
10
+ export type GuideLanguage = 'python' | 'typescript' | 'javascript' | 'swift' | 'curl' | 'belt'
11
+
12
+ export interface GuideApp {
13
+ name: string
14
+ namespace?: string
15
+ description?: string
16
+ }
17
+
18
+ export interface GuideVersion {
19
+ short_id?: string
20
+ input_schema?: any
21
+ output_schema?: any
22
+ setup_schema?: any
23
+ functions?: Record<string, { name?: string; description?: string; input_schema?: any; output_schema?: any }>
24
+ default_function?: string
25
+ }
26
+
27
+ /** Where a key is made. /settings/<path> on the app opens that settings page. */
28
+ export const API_KEYS_URL = 'https://app.inference.sh/settings/keys'
29
+
30
+ export function generateGuideMarkdown(
31
+ app: GuideApp,
32
+ version: GuideVersion | undefined,
33
+ language: GuideLanguage = 'curl',
34
+ functionName?: string | null,
35
+ keysUrl: string = API_KEYS_URL,
36
+ ): string {
37
+ const namespace = app.namespace || ''
38
+ const shortId = version?.short_id
39
+ const defaultFn = version?.default_function || 'run'
40
+ const effectiveFn = functionName || defaultFn
41
+ const func = version?.functions?.[effectiveFn]
42
+ const schema = func?.input_schema || version?.input_schema || {}
43
+ const outputSchemaObj = func?.output_schema || version?.output_schema
44
+
45
+ // :function only when it is not the default of a multi-function app
46
+ const hasManyFunctions = version?.functions && Object.keys(version.functions).length > 1
47
+ const fnSuffix = (hasManyFunctions && effectiveFn !== defaultFn) ? `:${effectiveFn}` : ''
48
+ const versionSuffix = shortId ? `@${shortId}` : ''
49
+ const appRef = `${namespace}/${app.name}${versionSuffix}${fnSuffix}`
50
+
51
+ const params = { appRef, shortId, input: exampleInput(schema) }
52
+ const resultExamples = generateResultOnlyExamples(params)
53
+ const streamExamples = generateStreamingExamples(params)
54
+ const backgroundExamples = generateBackgroundExamples(params)
55
+ const fileEx = generateFileExamples(appRef)[language]
56
+ const keySetup = apiKeySetup[language] || apiKeySetup.curl
57
+ const installCmd = installCommands[language]
58
+ const codeLang = (language === 'curl' || language === 'belt') ? 'bash' : language
59
+ // swift installs through Package.swift, not a shell command
60
+ const installLang = language === 'swift' ? 'swift' : 'bash'
61
+
62
+ const inputFields = schema ? getFieldsFromSchema(schema).fields : []
63
+ const outputFields = outputSchemaObj ? getFieldsFromSchema(outputSchemaObj).fields : []
64
+
65
+ const code = (lang: string, body: string) => ['```' + lang, body.trim(), '```']
66
+
67
+ const lines = [
68
+ `# ${app.name} API Guide`,
69
+ '',
70
+ '## About',
71
+ `${app.description?.toLowerCase() || `Run ${app.name} via API.`}`,
72
+ '',
73
+ '## Get an API Key',
74
+ language === 'belt'
75
+ ? `Create one in [settings → api keys](${keysUrl}), or sign in with \`belt login\`. The examples read it from \`${keySetup.variable}\`.`
76
+ : `Create one in [settings → api keys](${keysUrl}). The examples read it from \`${keySetup.variable}\`.`,
77
+ '',
78
+ ...code('bash', keySetup.command),
79
+ '',
80
+ '## 1. Calling the API',
81
+ '',
82
+ ...(installCmd
83
+ ? ['### Install the Client', '', ...code(installLang, installCmd.command)]
84
+ : ['### Base URL', '', '`https://api.inference.sh`. No version prefix. Every response wraps its result in a `data` field.']),
85
+ '',
86
+ '### Run and Get Result',
87
+ language === 'curl'
88
+ ? 'Send `"wait": true` to hold the connection until the task finishes. Without it the call returns right away with the task still running.'
89
+ : 'Submit a request and wait for the final result.',
90
+ '',
91
+ ...code(codeLang, resultExamples[language] || ''),
92
+ '',
93
+ '### Stream Live Updates',
94
+ language === 'curl'
95
+ ? 'Send `"stream": true` and the response is a server-sent event stream that ends when the task does.'
96
+ : 'Get real-time progress updates as the task runs.',
97
+ '',
98
+ ...code(codeLang, streamExamples[language] || ''),
99
+ '',
100
+ '### Run in the Background',
101
+ 'Start the task and get its id back right away, then check on it with `GET /tasks/{id}`. It is done when `status` is 10 (completed), 11 (failed) or 12 (cancelled).',
102
+ '',
103
+ ...code(codeLang, backgroundExamples[language] || ''),
104
+ '',
105
+ '## 2. Files',
106
+ '',
107
+ '### Automatic Upload',
108
+ '',
109
+ ...code(codeLang, fileEx.auto),
110
+ '',
111
+ '### Manual Upload',
112
+ '',
113
+ ...code(codeLang, fileEx.manual),
114
+ '',
115
+ '## 3. Schema',
116
+ '',
117
+ '### Input',
118
+ inputFields.length > 0
119
+ ? inputFields.map(f => `- **${f.key}** (${f.type || f.resolved?.type || 'any'})${f.isRequired ? ' *required*' : ''}: ${f.description || f.title || ''}`).join('\n')
120
+ : 'No input parameters defined.',
121
+ '',
122
+ '### Output',
123
+ outputFields.length > 0
124
+ ? outputFields.map(f => `- **${f.key}** (${f.type || f.resolved?.type || 'any'}): ${f.description || f.title || ''}`).join('\n')
125
+ : 'See task output for result format.',
126
+ ]
127
+
128
+ return lines.join('\n')
129
+ }