@brainfish-ai/devdoc 0.1.21

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 (268) hide show
  1. package/LICENSE +33 -0
  2. package/README.md +415 -0
  3. package/bin/devdoc.js +13 -0
  4. package/dist/cli/commands/build.d.ts +5 -0
  5. package/dist/cli/commands/build.js +87 -0
  6. package/dist/cli/commands/check.d.ts +1 -0
  7. package/dist/cli/commands/check.js +143 -0
  8. package/dist/cli/commands/create.d.ts +24 -0
  9. package/dist/cli/commands/create.js +387 -0
  10. package/dist/cli/commands/deploy.d.ts +9 -0
  11. package/dist/cli/commands/deploy.js +433 -0
  12. package/dist/cli/commands/dev.d.ts +6 -0
  13. package/dist/cli/commands/dev.js +139 -0
  14. package/dist/cli/commands/init.d.ts +11 -0
  15. package/dist/cli/commands/init.js +238 -0
  16. package/dist/cli/commands/keys.d.ts +12 -0
  17. package/dist/cli/commands/keys.js +165 -0
  18. package/dist/cli/commands/start.d.ts +5 -0
  19. package/dist/cli/commands/start.js +56 -0
  20. package/dist/cli/commands/upload.d.ts +13 -0
  21. package/dist/cli/commands/upload.js +238 -0
  22. package/dist/cli/commands/whoami.d.ts +8 -0
  23. package/dist/cli/commands/whoami.js +91 -0
  24. package/dist/cli/index.d.ts +1 -0
  25. package/dist/cli/index.js +106 -0
  26. package/dist/config/index.d.ts +80 -0
  27. package/dist/config/index.js +133 -0
  28. package/dist/constants.d.ts +9 -0
  29. package/dist/constants.js +13 -0
  30. package/dist/index.d.ts +7 -0
  31. package/dist/index.js +12 -0
  32. package/dist/utils/logger.d.ts +16 -0
  33. package/dist/utils/logger.js +61 -0
  34. package/dist/utils/paths.d.ts +16 -0
  35. package/dist/utils/paths.js +50 -0
  36. package/package.json +51 -0
  37. package/renderer/app/api/assets/[...path]/route.ts +123 -0
  38. package/renderer/app/api/assets/route.ts +124 -0
  39. package/renderer/app/api/assets/upload/route.ts +177 -0
  40. package/renderer/app/api/auth-schemes/route.ts +77 -0
  41. package/renderer/app/api/chat/route.ts +858 -0
  42. package/renderer/app/api/codegen/route.ts +72 -0
  43. package/renderer/app/api/collections/route.ts +1016 -0
  44. package/renderer/app/api/debug/route.ts +53 -0
  45. package/renderer/app/api/deploy/route.ts +234 -0
  46. package/renderer/app/api/device/route.ts +42 -0
  47. package/renderer/app/api/docs/route.ts +187 -0
  48. package/renderer/app/api/keys/regenerate/route.ts +80 -0
  49. package/renderer/app/api/openapi-spec/route.ts +151 -0
  50. package/renderer/app/api/projects/[slug]/route.ts +153 -0
  51. package/renderer/app/api/projects/[slug]/stats/route.ts +96 -0
  52. package/renderer/app/api/projects/register/route.ts +152 -0
  53. package/renderer/app/api/proxy/route.ts +149 -0
  54. package/renderer/app/api/proxy-stream/route.ts +168 -0
  55. package/renderer/app/api/redirects/route.ts +47 -0
  56. package/renderer/app/api/schema/route.ts +65 -0
  57. package/renderer/app/api/subdomains/check/route.ts +172 -0
  58. package/renderer/app/api/suggestions/route.ts +144 -0
  59. package/renderer/app/favicon.ico +0 -0
  60. package/renderer/app/globals.css +1103 -0
  61. package/renderer/app/layout.tsx +47 -0
  62. package/renderer/app/llms-full.txt/route.ts +346 -0
  63. package/renderer/app/llms.txt/route.ts +279 -0
  64. package/renderer/app/page.tsx +14 -0
  65. package/renderer/app/robots.txt/route.ts +84 -0
  66. package/renderer/app/sitemap.xml/route.ts +199 -0
  67. package/renderer/components/docs/index.ts +12 -0
  68. package/renderer/components/docs/mdx/accordion.tsx +169 -0
  69. package/renderer/components/docs/mdx/badge.tsx +132 -0
  70. package/renderer/components/docs/mdx/callouts.tsx +154 -0
  71. package/renderer/components/docs/mdx/cards.tsx +213 -0
  72. package/renderer/components/docs/mdx/changelog.tsx +120 -0
  73. package/renderer/components/docs/mdx/code-block.tsx +186 -0
  74. package/renderer/components/docs/mdx/code-group.tsx +421 -0
  75. package/renderer/components/docs/mdx/file-embeds.tsx +105 -0
  76. package/renderer/components/docs/mdx/frame.tsx +112 -0
  77. package/renderer/components/docs/mdx/highlight.tsx +151 -0
  78. package/renderer/components/docs/mdx/iframe.tsx +134 -0
  79. package/renderer/components/docs/mdx/image.tsx +235 -0
  80. package/renderer/components/docs/mdx/index.ts +204 -0
  81. package/renderer/components/docs/mdx/mermaid.tsx +240 -0
  82. package/renderer/components/docs/mdx/param-field.tsx +200 -0
  83. package/renderer/components/docs/mdx/steps.tsx +113 -0
  84. package/renderer/components/docs/mdx/tabs.tsx +86 -0
  85. package/renderer/components/docs/mdx-renderer.tsx +100 -0
  86. package/renderer/components/docs/navigation/breadcrumbs.tsx +76 -0
  87. package/renderer/components/docs/navigation/index.ts +8 -0
  88. package/renderer/components/docs/navigation/page-nav.tsx +64 -0
  89. package/renderer/components/docs/navigation/sidebar.tsx +515 -0
  90. package/renderer/components/docs/navigation/toc.tsx +113 -0
  91. package/renderer/components/docs/notice.tsx +105 -0
  92. package/renderer/components/docs-header.tsx +274 -0
  93. package/renderer/components/docs-viewer/agent/agent-chat.tsx +2076 -0
  94. package/renderer/components/docs-viewer/agent/cards/debug-context-card.tsx +90 -0
  95. package/renderer/components/docs-viewer/agent/cards/endpoint-context-card.tsx +49 -0
  96. package/renderer/components/docs-viewer/agent/cards/index.tsx +50 -0
  97. package/renderer/components/docs-viewer/agent/cards/response-options-card.tsx +212 -0
  98. package/renderer/components/docs-viewer/agent/cards/types.ts +84 -0
  99. package/renderer/components/docs-viewer/agent/chat-message.tsx +17 -0
  100. package/renderer/components/docs-viewer/agent/index.tsx +6 -0
  101. package/renderer/components/docs-viewer/agent/messages/assistant-message.tsx +119 -0
  102. package/renderer/components/docs-viewer/agent/messages/chat-message.tsx +46 -0
  103. package/renderer/components/docs-viewer/agent/messages/index.ts +17 -0
  104. package/renderer/components/docs-viewer/agent/messages/tool-call-display.tsx +721 -0
  105. package/renderer/components/docs-viewer/agent/messages/types.ts +61 -0
  106. package/renderer/components/docs-viewer/agent/messages/typing-indicator.tsx +24 -0
  107. package/renderer/components/docs-viewer/agent/messages/user-message.tsx +51 -0
  108. package/renderer/components/docs-viewer/code-editor/index.tsx +2 -0
  109. package/renderer/components/docs-viewer/code-editor/notes-mode.tsx +1283 -0
  110. package/renderer/components/docs-viewer/content/changelog-page.tsx +331 -0
  111. package/renderer/components/docs-viewer/content/doc-page.tsx +285 -0
  112. package/renderer/components/docs-viewer/content/documentation-viewer.tsx +17 -0
  113. package/renderer/components/docs-viewer/content/index.tsx +29 -0
  114. package/renderer/components/docs-viewer/content/introduction.tsx +21 -0
  115. package/renderer/components/docs-viewer/content/request-details.tsx +330 -0
  116. package/renderer/components/docs-viewer/content/sections/auth.tsx +69 -0
  117. package/renderer/components/docs-viewer/content/sections/body.tsx +66 -0
  118. package/renderer/components/docs-viewer/content/sections/headers.tsx +43 -0
  119. package/renderer/components/docs-viewer/content/sections/overview.tsx +40 -0
  120. package/renderer/components/docs-viewer/content/sections/parameters.tsx +43 -0
  121. package/renderer/components/docs-viewer/content/sections/responses.tsx +87 -0
  122. package/renderer/components/docs-viewer/global-auth-modal.tsx +352 -0
  123. package/renderer/components/docs-viewer/index.tsx +1466 -0
  124. package/renderer/components/docs-viewer/playground/auth-editor.tsx +280 -0
  125. package/renderer/components/docs-viewer/playground/body-editor.tsx +221 -0
  126. package/renderer/components/docs-viewer/playground/code-editor.tsx +224 -0
  127. package/renderer/components/docs-viewer/playground/code-snippet.tsx +387 -0
  128. package/renderer/components/docs-viewer/playground/graphql-playground.tsx +745 -0
  129. package/renderer/components/docs-viewer/playground/index.tsx +671 -0
  130. package/renderer/components/docs-viewer/playground/key-value-editor.tsx +261 -0
  131. package/renderer/components/docs-viewer/playground/method-selector.tsx +60 -0
  132. package/renderer/components/docs-viewer/playground/request-builder.tsx +179 -0
  133. package/renderer/components/docs-viewer/playground/request-tabs.tsx +237 -0
  134. package/renderer/components/docs-viewer/playground/response-cards/idle-card.tsx +21 -0
  135. package/renderer/components/docs-viewer/playground/response-cards/index.tsx +93 -0
  136. package/renderer/components/docs-viewer/playground/response-cards/loading-card.tsx +16 -0
  137. package/renderer/components/docs-viewer/playground/response-cards/network-error-card.tsx +23 -0
  138. package/renderer/components/docs-viewer/playground/response-cards/response-body-card.tsx +268 -0
  139. package/renderer/components/docs-viewer/playground/response-cards/types.ts +82 -0
  140. package/renderer/components/docs-viewer/playground/response-viewer.tsx +43 -0
  141. package/renderer/components/docs-viewer/search/index.ts +2 -0
  142. package/renderer/components/docs-viewer/search/search-dialog.tsx +331 -0
  143. package/renderer/components/docs-viewer/search/use-search.ts +117 -0
  144. package/renderer/components/docs-viewer/shared/markdown-renderer.tsx +431 -0
  145. package/renderer/components/docs-viewer/shared/method-badge.tsx +41 -0
  146. package/renderer/components/docs-viewer/shared/schema-viewer.tsx +349 -0
  147. package/renderer/components/docs-viewer/sidebar/collection-tree.tsx +239 -0
  148. package/renderer/components/docs-viewer/sidebar/endpoint-options.tsx +316 -0
  149. package/renderer/components/docs-viewer/sidebar/index.tsx +343 -0
  150. package/renderer/components/docs-viewer/sidebar/right-sidebar.tsx +202 -0
  151. package/renderer/components/docs-viewer/sidebar/sidebar-group.tsx +118 -0
  152. package/renderer/components/docs-viewer/sidebar/sidebar-item.tsx +226 -0
  153. package/renderer/components/docs-viewer/sidebar/sidebar-section.tsx +52 -0
  154. package/renderer/components/theme-provider.tsx +11 -0
  155. package/renderer/components/theme-toggle.tsx +76 -0
  156. package/renderer/components/ui/badge.tsx +46 -0
  157. package/renderer/components/ui/button.tsx +59 -0
  158. package/renderer/components/ui/dialog.tsx +118 -0
  159. package/renderer/components/ui/dropdown-menu.tsx +257 -0
  160. package/renderer/components/ui/input.tsx +21 -0
  161. package/renderer/components/ui/label.tsx +24 -0
  162. package/renderer/components/ui/navigation-menu.tsx +168 -0
  163. package/renderer/components/ui/select.tsx +190 -0
  164. package/renderer/components/ui/spinner.tsx +114 -0
  165. package/renderer/components/ui/tabs.tsx +66 -0
  166. package/renderer/components/ui/tooltip.tsx +61 -0
  167. package/renderer/hooks/use-code-copy.ts +88 -0
  168. package/renderer/hooks/use-openapi-title.ts +44 -0
  169. package/renderer/lib/api-docs/agent/index.ts +6 -0
  170. package/renderer/lib/api-docs/agent/indexer.ts +323 -0
  171. package/renderer/lib/api-docs/agent/spec-summary.ts +335 -0
  172. package/renderer/lib/api-docs/agent/types.ts +116 -0
  173. package/renderer/lib/api-docs/auth/auth-context.tsx +225 -0
  174. package/renderer/lib/api-docs/auth/auth-storage.ts +87 -0
  175. package/renderer/lib/api-docs/auth/crypto.ts +89 -0
  176. package/renderer/lib/api-docs/auth/index.ts +4 -0
  177. package/renderer/lib/api-docs/code-editor/db.ts +164 -0
  178. package/renderer/lib/api-docs/code-editor/hooks.ts +266 -0
  179. package/renderer/lib/api-docs/code-editor/index.ts +6 -0
  180. package/renderer/lib/api-docs/code-editor/mode-context.tsx +207 -0
  181. package/renderer/lib/api-docs/code-editor/types.ts +105 -0
  182. package/renderer/lib/api-docs/codegen/definitions.ts +297 -0
  183. package/renderer/lib/api-docs/codegen/har.ts +251 -0
  184. package/renderer/lib/api-docs/codegen/index.ts +159 -0
  185. package/renderer/lib/api-docs/factories.ts +151 -0
  186. package/renderer/lib/api-docs/index.ts +17 -0
  187. package/renderer/lib/api-docs/mobile-context.tsx +112 -0
  188. package/renderer/lib/api-docs/navigation-context.tsx +88 -0
  189. package/renderer/lib/api-docs/parsers/graphql/README.md +129 -0
  190. package/renderer/lib/api-docs/parsers/graphql/index.ts +91 -0
  191. package/renderer/lib/api-docs/parsers/graphql/parser.ts +491 -0
  192. package/renderer/lib/api-docs/parsers/graphql/transformer.ts +246 -0
  193. package/renderer/lib/api-docs/parsers/graphql/types.ts +283 -0
  194. package/renderer/lib/api-docs/parsers/openapi/README.md +32 -0
  195. package/renderer/lib/api-docs/parsers/openapi/dereferencer.ts +60 -0
  196. package/renderer/lib/api-docs/parsers/openapi/extractors/auth.ts +574 -0
  197. package/renderer/lib/api-docs/parsers/openapi/extractors/body.ts +403 -0
  198. package/renderer/lib/api-docs/parsers/openapi/extractors/index.ts +232 -0
  199. package/renderer/lib/api-docs/parsers/openapi/index.ts +171 -0
  200. package/renderer/lib/api-docs/parsers/openapi/transformer.ts +277 -0
  201. package/renderer/lib/api-docs/parsers/openapi/validator.ts +31 -0
  202. package/renderer/lib/api-docs/playground/context.tsx +107 -0
  203. package/renderer/lib/api-docs/playground/navigation-context.tsx +124 -0
  204. package/renderer/lib/api-docs/playground/request-builder.ts +223 -0
  205. package/renderer/lib/api-docs/playground/request-runner.ts +282 -0
  206. package/renderer/lib/api-docs/playground/types.ts +35 -0
  207. package/renderer/lib/api-docs/types.ts +269 -0
  208. package/renderer/lib/api-docs/utils.ts +311 -0
  209. package/renderer/lib/cache.ts +193 -0
  210. package/renderer/lib/docs/config/index.ts +29 -0
  211. package/renderer/lib/docs/config/loader.ts +142 -0
  212. package/renderer/lib/docs/config/schema.ts +298 -0
  213. package/renderer/lib/docs/index.ts +12 -0
  214. package/renderer/lib/docs/mdx/compiler.ts +176 -0
  215. package/renderer/lib/docs/mdx/frontmatter.ts +80 -0
  216. package/renderer/lib/docs/mdx/index.ts +26 -0
  217. package/renderer/lib/docs/navigation/generator.ts +348 -0
  218. package/renderer/lib/docs/navigation/index.ts +12 -0
  219. package/renderer/lib/docs/navigation/types.ts +123 -0
  220. package/renderer/lib/docs-navigation-context.tsx +80 -0
  221. package/renderer/lib/multi-tenant/context.ts +105 -0
  222. package/renderer/lib/storage/blob.ts +845 -0
  223. package/renderer/lib/utils.ts +6 -0
  224. package/renderer/next.config.ts +76 -0
  225. package/renderer/package.json +66 -0
  226. package/renderer/postcss.config.mjs +5 -0
  227. package/renderer/public/assets/images/screenshot.png +0 -0
  228. package/renderer/public/assets/logo/dark.svg +9 -0
  229. package/renderer/public/assets/logo/light.svg +9 -0
  230. package/renderer/public/assets/logo.svg +9 -0
  231. package/renderer/public/file.svg +1 -0
  232. package/renderer/public/globe.svg +1 -0
  233. package/renderer/public/icon.png +0 -0
  234. package/renderer/public/logo.svg +9 -0
  235. package/renderer/public/window.svg +1 -0
  236. package/renderer/tsconfig.json +28 -0
  237. package/templates/basic/README.md +139 -0
  238. package/templates/basic/assets/favicon.svg +4 -0
  239. package/templates/basic/assets/logo.svg +9 -0
  240. package/templates/basic/docs.json +47 -0
  241. package/templates/basic/guides/configuration.mdx +149 -0
  242. package/templates/basic/guides/overview.mdx +96 -0
  243. package/templates/basic/index.mdx +39 -0
  244. package/templates/basic/package.json +14 -0
  245. package/templates/basic/quickstart.mdx +92 -0
  246. package/templates/basic/vercel.json +6 -0
  247. package/templates/graphql/README.md +139 -0
  248. package/templates/graphql/api-reference/schema.graphql +305 -0
  249. package/templates/graphql/assets/favicon.svg +4 -0
  250. package/templates/graphql/assets/logo.svg +9 -0
  251. package/templates/graphql/docs.json +54 -0
  252. package/templates/graphql/guides/configuration.mdx +149 -0
  253. package/templates/graphql/guides/overview.mdx +96 -0
  254. package/templates/graphql/index.mdx +39 -0
  255. package/templates/graphql/package.json +14 -0
  256. package/templates/graphql/quickstart.mdx +92 -0
  257. package/templates/graphql/vercel.json +6 -0
  258. package/templates/openapi/README.md +139 -0
  259. package/templates/openapi/api-reference/openapi.json +419 -0
  260. package/templates/openapi/assets/favicon.svg +4 -0
  261. package/templates/openapi/assets/logo.svg +9 -0
  262. package/templates/openapi/docs.json +61 -0
  263. package/templates/openapi/guides/configuration.mdx +149 -0
  264. package/templates/openapi/guides/overview.mdx +96 -0
  265. package/templates/openapi/index.mdx +39 -0
  266. package/templates/openapi/package.json +14 -0
  267. package/templates/openapi/quickstart.mdx +92 -0
  268. package/templates/openapi/vercel.json +6 -0
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Agent Mode Types
3
+ * Types for the AI-powered API documentation assistant
4
+ */
5
+
6
+ import type { BrainfishRESTRequest } from '../types'
7
+
8
+ // Chat message types
9
+ export interface AgentMessage {
10
+ id: string
11
+ role: 'user' | 'assistant' | 'system'
12
+ content: string
13
+ createdAt: Date
14
+ toolInvocations?: ToolInvocation[]
15
+ }
16
+
17
+ export interface ToolInvocation {
18
+ toolCallId: string
19
+ toolName: string
20
+ args: Record<string, unknown>
21
+ result?: unknown
22
+ state: 'pending' | 'result' | 'error'
23
+ }
24
+
25
+ // Endpoint index for AI context
26
+ export interface EndpointIndex {
27
+ id: string
28
+ name: string
29
+ method: string
30
+ path: string
31
+ description: string | null
32
+ parameters: string[]
33
+ hasBody: boolean
34
+ tags: string[]
35
+ }
36
+
37
+ // Prefill data from agent
38
+ export interface PrefillData {
39
+ params?: Array<{ key: string; value: string }>
40
+ headers?: Array<{ key: string; value: string }>
41
+ body?: string
42
+ pathVariables?: Array<{ key: string; value: string }>
43
+ }
44
+
45
+ // Agent context state
46
+ export interface AgentState {
47
+ mode: 'agent' | 'docs'
48
+ messages: AgentMessage[]
49
+ isLoading: boolean
50
+ currentEndpoint: BrainfishRESTRequest | null
51
+ }
52
+
53
+ // Tool definitions
54
+ export type AgentToolName =
55
+ | 'search_endpoints'
56
+ | 'navigate_to_endpoint'
57
+ | 'prefill_parameters'
58
+ | 'get_endpoint_details'
59
+ | 'get_current_request'
60
+ | 'check_request_validity'
61
+ | 'send_request'
62
+ | 'switch_to_notes'
63
+ | 'list_notes'
64
+ | 'open_file'
65
+ | 'create_folder'
66
+ | 'create_file'
67
+ | 'write_file'
68
+ | 'delete_file'
69
+
70
+ // Request validation result
71
+ export interface RequestValidationResult {
72
+ isValid: boolean
73
+ endpoint: {
74
+ id: string
75
+ name: string
76
+ method: string
77
+ path: string
78
+ }
79
+ validation: {
80
+ pathParams: {
81
+ required: string[]
82
+ filled: string[]
83
+ missing: string[]
84
+ }
85
+ queryParams: {
86
+ required: string[]
87
+ filled: string[]
88
+ missing: string[]
89
+ }
90
+ body: {
91
+ required: boolean
92
+ hasContent: boolean
93
+ requiredFields: string[]
94
+ missingFields: string[]
95
+ }
96
+ auth: {
97
+ required: boolean
98
+ configured: boolean
99
+ type: string | null
100
+ }
101
+ }
102
+ summary: string
103
+ }
104
+
105
+ // Search results
106
+ export interface EndpointSearchResult {
107
+ endpoint: EndpointIndex
108
+ score: number
109
+ matchReason: string
110
+ }
111
+
112
+ // Navigation action
113
+ export interface NavigationAction {
114
+ endpointId: string
115
+ reason: string
116
+ }
@@ -0,0 +1,225 @@
1
+ 'use client'
2
+
3
+ import {
4
+ createContext,
5
+ useContext,
6
+ useState,
7
+ useEffect,
8
+ useCallback,
9
+ useRef,
10
+ type ReactNode,
11
+ } from 'react'
12
+ import type { BrainfishRESTAuth } from '../types'
13
+ import { authStorage, fetchDeviceId } from './auth-storage'
14
+
15
+ /** Security scheme info from OpenAPI spec */
16
+ export interface SpecSecurityScheme {
17
+ name: string
18
+ type: 'basic' | 'bearer' | 'api-key' | 'oauth2' | 'none'
19
+ auth: BrainfishRESTAuth
20
+ description?: string
21
+ }
22
+
23
+ interface AuthContextValue {
24
+ /** Current global authentication configuration */
25
+ globalAuth: BrainfishRESTAuth | null
26
+ /** Whether global auth is configured and active */
27
+ isAuthenticated: boolean
28
+ /** Set the global authentication */
29
+ setGlobalAuth: (auth: BrainfishRESTAuth) => void
30
+ /** Clear the global authentication */
31
+ clearAuth: () => void
32
+ /** Get effective auth for an endpoint (global or override) */
33
+ getEffectiveAuth: (endpointAuth: BrainfishRESTAuth) => BrainfishRESTAuth
34
+ /** Check if using global auth for an endpoint */
35
+ isUsingGlobalAuth: (endpointAuth: BrainfishRESTAuth) => boolean
36
+ /** Security schemes from the OpenAPI spec */
37
+ specSchemes: SpecSecurityScheme[]
38
+ /** Default auth recommended by the spec */
39
+ specDefaultAuth: BrainfishRESTAuth | null
40
+ }
41
+
42
+ const AuthContext = createContext<AuthContextValue | null>(null)
43
+
44
+ const DEFAULT_AUTH: BrainfishRESTAuth = { authType: 'none', authActive: true }
45
+
46
+ interface AuthProviderProps {
47
+ children: ReactNode
48
+ }
49
+
50
+ export function AuthProvider({ children }: AuthProviderProps) {
51
+ const [globalAuth, setGlobalAuthState] = useState<BrainfishRESTAuth | null>(null)
52
+ const [isInitialized, setIsInitialized] = useState(false)
53
+ const [specSchemes, setSpecSchemes] = useState<SpecSecurityScheme[]>([])
54
+ const [specDefaultAuth, setSpecDefaultAuth] = useState<BrainfishRESTAuth | null>(null)
55
+ const deviceIdRef = useRef<string | null>(null)
56
+
57
+ // Load auth from localStorage on mount (async with encryption)
58
+ useEffect(() => {
59
+ async function initializeAuth() {
60
+ try {
61
+ // Fetch device ID from backend (required for encryption)
62
+ const deviceId = await fetchDeviceId()
63
+ deviceIdRef.current = deviceId
64
+
65
+ // Load encrypted auth from storage
66
+ const stored = await authStorage.load(deviceId)
67
+ if (stored) {
68
+ setGlobalAuthState(stored)
69
+ }
70
+
71
+ // Fetch security schemes from the spec
72
+ const schemesResponse = await fetch('/api/auth-schemes')
73
+ if (schemesResponse.ok) {
74
+ const data = await schemesResponse.json()
75
+ setSpecSchemes(data.schemes || [])
76
+ setSpecDefaultAuth(data.defaultAuth || null)
77
+ }
78
+ } catch (error) {
79
+ console.error('[AuthProvider] Failed to initialize auth:', error)
80
+ // Clear any potentially corrupted data
81
+ authStorage.clear()
82
+ } finally {
83
+ setIsInitialized(true)
84
+ }
85
+ }
86
+
87
+ initializeAuth()
88
+ }, [])
89
+
90
+ const setGlobalAuth = useCallback(async (auth: BrainfishRESTAuth) => {
91
+ if (!deviceIdRef.current) {
92
+ console.error('[AuthProvider] Cannot save auth: no device ID')
93
+ return
94
+ }
95
+ setGlobalAuthState(auth)
96
+ await authStorage.save(auth, deviceIdRef.current)
97
+ }, [])
98
+
99
+ const clearAuth = useCallback(() => {
100
+ setGlobalAuthState(null)
101
+ authStorage.clear()
102
+ }, [])
103
+
104
+ /**
105
+ * Get effective auth for an endpoint
106
+ * - If endpoint has 'inherit' auth type, use global auth
107
+ * - If endpoint has 'none' auth type and global is set, use global
108
+ * - Otherwise use endpoint's own auth
109
+ */
110
+ const getEffectiveAuth = useCallback(
111
+ (endpointAuth: BrainfishRESTAuth): BrainfishRESTAuth => {
112
+ // If endpoint explicitly wants to inherit, use global
113
+ if (endpointAuth.authType === 'inherit') {
114
+ return globalAuth || DEFAULT_AUTH
115
+ }
116
+
117
+ // If endpoint has no auth but global is configured, suggest global
118
+ // but don't override - this allows explicit "no auth" endpoints
119
+ if (endpointAuth.authType === 'none' && globalAuth && globalAuth.authType !== 'none') {
120
+ // Return global auth but the UI should indicate it's using global
121
+ return globalAuth
122
+ }
123
+
124
+ // Use endpoint's own auth
125
+ return endpointAuth
126
+ },
127
+ [globalAuth]
128
+ )
129
+
130
+ /**
131
+ * Check if an endpoint is using global auth
132
+ */
133
+ const isUsingGlobalAuth = useCallback(
134
+ (endpointAuth: BrainfishRESTAuth): boolean => {
135
+ if (!globalAuth || globalAuth.authType === 'none') {
136
+ return false
137
+ }
138
+
139
+ // Using global if inheriting or if endpoint has no auth
140
+ return (
141
+ endpointAuth.authType === 'inherit' ||
142
+ endpointAuth.authType === 'none'
143
+ )
144
+ },
145
+ [globalAuth]
146
+ )
147
+
148
+ const isAuthenticated =
149
+ globalAuth !== null && globalAuth.authType !== 'none'
150
+
151
+ const value: AuthContextValue = {
152
+ globalAuth,
153
+ isAuthenticated,
154
+ setGlobalAuth,
155
+ clearAuth,
156
+ getEffectiveAuth,
157
+ isUsingGlobalAuth,
158
+ specSchemes,
159
+ specDefaultAuth,
160
+ }
161
+
162
+ // Don't render until we've checked sessionStorage
163
+ if (!isInitialized) {
164
+ return null
165
+ }
166
+
167
+ return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
168
+ }
169
+
170
+ export function useAuth() {
171
+ const context = useContext(AuthContext)
172
+ if (!context) {
173
+ throw new Error('useAuth must be used within an AuthProvider')
174
+ }
175
+ return context
176
+ }
177
+
178
+ /**
179
+ * Get a human-readable label for auth type
180
+ */
181
+ export function getAuthTypeLabel(auth: BrainfishRESTAuth | null | undefined): string {
182
+ if (!auth || !auth.authType) {
183
+ return 'Not configured'
184
+ }
185
+
186
+ switch (auth.authType) {
187
+ case 'none':
188
+ return 'No Auth'
189
+ case 'inherit':
190
+ return 'Inherited'
191
+ case 'basic':
192
+ return 'Basic Auth'
193
+ case 'bearer':
194
+ return 'Bearer Token'
195
+ case 'api-key':
196
+ return `API Key (${auth.key || 'key'})`
197
+ case 'oauth-2':
198
+ return 'OAuth 2.0'
199
+ default:
200
+ return 'Not configured'
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Get a masked preview of auth value
206
+ */
207
+ export function getAuthPreview(auth: BrainfishRESTAuth): string {
208
+ switch (auth.authType) {
209
+ case 'none':
210
+ case 'inherit':
211
+ return ''
212
+ case 'basic':
213
+ return auth.username ? `${auth.username}:****` : ''
214
+ case 'bearer':
215
+ return auth.token ? `${auth.token.slice(0, 8)}••••` : ''
216
+ case 'api-key':
217
+ return auth.value ? `${auth.value.slice(0, 4)}••••` : ''
218
+ case 'oauth-2':
219
+ return auth.grantTypeInfo.token
220
+ ? `${auth.grantTypeInfo.token.slice(0, 8)}••••`
221
+ : ''
222
+ default:
223
+ return ''
224
+ }
225
+ }
@@ -0,0 +1,87 @@
1
+ 'use client'
2
+
3
+ import type { BrainfishRESTAuth } from '../types'
4
+ import { encrypt, decrypt } from './crypto'
5
+
6
+ const AUTH_STORAGE_KEY = 'brainfish-api-global-auth'
7
+
8
+ export interface StoredAuth {
9
+ globalAuth: BrainfishRESTAuth
10
+ timestamp: number
11
+ }
12
+
13
+ /**
14
+ * Fetch device ID from backend API
15
+ * The device ID is stored in an httpOnly cookie
16
+ * Throws error if device ID cannot be fetched (no fallback)
17
+ */
18
+ export async function fetchDeviceId(): Promise<string> {
19
+ const response = await fetch('/api/device', {
20
+ credentials: 'include',
21
+ })
22
+ if (!response.ok) {
23
+ throw new Error('Failed to fetch device ID')
24
+ }
25
+ const data = await response.json()
26
+ if (!data.deviceId) {
27
+ throw new Error('No device ID returned from server')
28
+ }
29
+ return data.deviceId
30
+ }
31
+
32
+ /**
33
+ * Encrypted localStorage wrapper for global authentication
34
+ * Uses device ID from backend as encryption key
35
+ * No fallbacks - encryption is required
36
+ */
37
+ export const authStorage = {
38
+ /**
39
+ * Save auth configuration to localStorage (encrypted)
40
+ * Requires valid device ID - throws if missing
41
+ */
42
+ async save(auth: BrainfishRESTAuth, deviceId: string): Promise<void> {
43
+ if (!deviceId) {
44
+ throw new Error('Device ID required for secure storage')
45
+ }
46
+ const data: StoredAuth = {
47
+ globalAuth: auth,
48
+ timestamp: Date.now(),
49
+ }
50
+ const encrypted = await encrypt(JSON.stringify(data), deviceId)
51
+ localStorage.setItem(AUTH_STORAGE_KEY, encrypted)
52
+ },
53
+
54
+ /**
55
+ * Load auth configuration from localStorage (decrypted)
56
+ * Returns null if no data or decryption fails
57
+ */
58
+ async load(deviceId: string): Promise<BrainfishRESTAuth | null> {
59
+ if (!deviceId) return null
60
+
61
+ const stored = localStorage.getItem(AUTH_STORAGE_KEY)
62
+ if (!stored) return null
63
+
64
+ try {
65
+ const jsonData = await decrypt(stored, deviceId)
66
+ const data = JSON.parse(jsonData) as StoredAuth
67
+
68
+ if (!data.globalAuth?.authType) {
69
+ localStorage.removeItem(AUTH_STORAGE_KEY)
70
+ return null
71
+ }
72
+
73
+ return data.globalAuth
74
+ } catch {
75
+ // Decryption failed - clear corrupted/invalid data
76
+ localStorage.removeItem(AUTH_STORAGE_KEY)
77
+ return null
78
+ }
79
+ },
80
+
81
+ /**
82
+ * Clear stored auth configuration
83
+ */
84
+ clear(): void {
85
+ localStorage.removeItem(AUTH_STORAGE_KEY)
86
+ },
87
+ }
@@ -0,0 +1,89 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Web Crypto API utilities for encrypting/decrypting auth credentials
5
+ * Uses AES-GCM with a key derived from device ID
6
+ */
7
+
8
+ const ALGORITHM = 'AES-GCM'
9
+ const KEY_LENGTH = 256
10
+ const IV_LENGTH = 12 // 96 bits for AES-GCM
11
+
12
+ /**
13
+ * Derive a crypto key from a device ID using PBKDF2
14
+ */
15
+ async function deriveKey(deviceId: string): Promise<CryptoKey> {
16
+ const encoder = new TextEncoder()
17
+ const keyMaterial = await crypto.subtle.importKey(
18
+ 'raw',
19
+ encoder.encode(deviceId),
20
+ 'PBKDF2',
21
+ false,
22
+ ['deriveKey']
23
+ )
24
+
25
+ // Use a fixed salt (could be made dynamic per-user if needed)
26
+ const salt = encoder.encode('brainfish-api-docs-v1')
27
+
28
+ return crypto.subtle.deriveKey(
29
+ {
30
+ name: 'PBKDF2',
31
+ salt,
32
+ iterations: 100000,
33
+ hash: 'SHA-256',
34
+ },
35
+ keyMaterial,
36
+ { name: ALGORITHM, length: KEY_LENGTH },
37
+ false,
38
+ ['encrypt', 'decrypt']
39
+ )
40
+ }
41
+
42
+ /**
43
+ * Encrypt a string using AES-GCM with device ID as key
44
+ */
45
+ export async function encrypt(plaintext: string, deviceId: string): Promise<string> {
46
+ const key = await deriveKey(deviceId)
47
+ const encoder = new TextEncoder()
48
+ const data = encoder.encode(plaintext)
49
+
50
+ // Generate random IV
51
+ const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH))
52
+
53
+ const encrypted = await crypto.subtle.encrypt(
54
+ { name: ALGORITHM, iv },
55
+ key,
56
+ data
57
+ )
58
+
59
+ // Combine IV + encrypted data and encode as base64
60
+ const combined = new Uint8Array(iv.length + encrypted.byteLength)
61
+ combined.set(iv)
62
+ combined.set(new Uint8Array(encrypted), iv.length)
63
+
64
+ return btoa(String.fromCharCode(...combined))
65
+ }
66
+
67
+ /**
68
+ * Decrypt a string using AES-GCM with device ID as key
69
+ */
70
+ export async function decrypt(ciphertext: string, deviceId: string): Promise<string> {
71
+ const key = await deriveKey(deviceId)
72
+
73
+ // Decode base64
74
+ const combined = Uint8Array.from(atob(ciphertext), (c) => c.charCodeAt(0))
75
+
76
+ // Extract IV and encrypted data
77
+ const iv = combined.slice(0, IV_LENGTH)
78
+ const encrypted = combined.slice(IV_LENGTH)
79
+
80
+ const decrypted = await crypto.subtle.decrypt(
81
+ { name: ALGORITHM, iv },
82
+ key,
83
+ encrypted
84
+ )
85
+
86
+ const decoder = new TextDecoder()
87
+ return decoder.decode(decrypted)
88
+ }
89
+
@@ -0,0 +1,4 @@
1
+ export { AuthProvider, useAuth, getAuthTypeLabel, getAuthPreview } from './auth-context'
2
+ export type { SpecSecurityScheme } from './auth-context'
3
+ export { authStorage, fetchDeviceId } from './auth-storage'
4
+ export { encrypt, decrypt } from './crypto'
@@ -0,0 +1,164 @@
1
+ import Dexie, { type Table } from 'dexie'
2
+ import type { Workspace, NoteFile, EditorState } from './types'
3
+
4
+ /**
5
+ * Simplified database for Notes workspace
6
+ * - One workspace per API (not multiple projects)
7
+ * - Notes are files for brainstorming (code, diagrams, markdown)
8
+ */
9
+ class NotesDatabase extends Dexie {
10
+ workspaces!: Table<Workspace>
11
+ notes!: Table<NoteFile>
12
+ editorStates!: Table<EditorState>
13
+
14
+ constructor() {
15
+ super('brainfish-notes')
16
+
17
+ this.version(1).stores({
18
+ // Single workspace per API
19
+ workspaces: 'id, apiSpecUrl, updatedAt',
20
+
21
+ // Notes/files in workspace
22
+ notes: 'id, workspaceId, [workspaceId+path], updatedAt',
23
+
24
+ // Editor state per workspace
25
+ editorStates: 'workspaceId',
26
+ })
27
+ }
28
+ }
29
+
30
+ // Singleton database instance
31
+ export const db = new NotesDatabase()
32
+
33
+ /**
34
+ * Generate a workspace ID from API spec URL
35
+ */
36
+ export function getWorkspaceId(apiSpecUrl: string): string {
37
+ // Simple hash for consistent ID
38
+ let hash = 0
39
+ for (let i = 0; i < apiSpecUrl.length; i++) {
40
+ const char = apiSpecUrl.charCodeAt(i)
41
+ hash = ((hash << 5) - hash) + char
42
+ hash = hash & hash // Convert to 32-bit integer
43
+ }
44
+ return `ws-${Math.abs(hash).toString(16)}`
45
+ }
46
+
47
+ /**
48
+ * Database operations - simplified for notes workspace
49
+ */
50
+ export const dbOperations = {
51
+ // Workspace operations (auto-created, one per API)
52
+
53
+ async getOrCreateWorkspace(apiSpecUrl: string, name: string): Promise<Workspace> {
54
+ const id = getWorkspaceId(apiSpecUrl)
55
+
56
+ let workspace = await db.workspaces.get(id)
57
+
58
+ if (!workspace) {
59
+ workspace = {
60
+ id,
61
+ name,
62
+ apiSpecUrl,
63
+ createdAt: new Date(),
64
+ updatedAt: new Date(),
65
+ }
66
+ await db.workspaces.add(workspace)
67
+ }
68
+
69
+ return workspace
70
+ },
71
+
72
+ async getWorkspace(id: string): Promise<Workspace | undefined> {
73
+ return db.workspaces.get(id)
74
+ },
75
+
76
+ async updateWorkspace(id: string, updates: Partial<Workspace>): Promise<void> {
77
+ await db.workspaces.update(id, {
78
+ ...updates,
79
+ updatedAt: new Date(),
80
+ })
81
+ },
82
+
83
+ // Note/file operations
84
+
85
+ async createNote(workspaceId: string, path: string, content: string = ''): Promise<NoteFile> {
86
+ const note: NoteFile = {
87
+ id: crypto.randomUUID(),
88
+ workspaceId,
89
+ path,
90
+ content,
91
+ createdAt: new Date(),
92
+ updatedAt: new Date(),
93
+ }
94
+ await db.notes.add(note)
95
+
96
+ // Update workspace timestamp
97
+ await db.workspaces.update(workspaceId, { updatedAt: new Date() })
98
+
99
+ return note
100
+ },
101
+
102
+ async getNote(workspaceId: string, path: string): Promise<NoteFile | undefined> {
103
+ return db.notes
104
+ .where('[workspaceId+path]')
105
+ .equals([workspaceId, path])
106
+ .first()
107
+ },
108
+
109
+ async listNotes(workspaceId: string): Promise<NoteFile[]> {
110
+ return db.notes
111
+ .where('workspaceId')
112
+ .equals(workspaceId)
113
+ .toArray()
114
+ },
115
+
116
+ async updateNote(workspaceId: string, path: string, content: string): Promise<void> {
117
+ await db.notes
118
+ .where('[workspaceId+path]')
119
+ .equals([workspaceId, path])
120
+ .modify({
121
+ content,
122
+ updatedAt: new Date(),
123
+ })
124
+
125
+ await db.workspaces.update(workspaceId, { updatedAt: new Date() })
126
+ },
127
+
128
+ async deleteNote(workspaceId: string, path: string): Promise<void> {
129
+ await db.notes
130
+ .where('[workspaceId+path]')
131
+ .equals([workspaceId, path])
132
+ .delete()
133
+
134
+ await db.workspaces.update(workspaceId, { updatedAt: new Date() })
135
+ },
136
+
137
+ async renameNote(workspaceId: string, oldPath: string, newPath: string): Promise<void> {
138
+ await db.notes
139
+ .where('[workspaceId+path]')
140
+ .equals([workspaceId, oldPath])
141
+ .modify({
142
+ path: newPath,
143
+ updatedAt: new Date(),
144
+ })
145
+
146
+ await db.workspaces.update(workspaceId, { updatedAt: new Date() })
147
+ },
148
+
149
+ // Editor state operations
150
+
151
+ async getEditorState(workspaceId: string): Promise<EditorState | undefined> {
152
+ return db.editorStates.get(workspaceId)
153
+ },
154
+
155
+ async saveEditorState(state: EditorState): Promise<void> {
156
+ await db.editorStates.put(state)
157
+ },
158
+
159
+ // Clear all notes in workspace (reset)
160
+ async clearWorkspace(workspaceId: string): Promise<void> {
161
+ await db.notes.where('workspaceId').equals(workspaceId).delete()
162
+ await db.editorStates.delete(workspaceId)
163
+ },
164
+ }