@open-mercato/ai-assistant 0.4.2-canary-c02407ff85

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 (193) hide show
  1. package/AGENTS.md +1090 -0
  2. package/README.md +607 -0
  3. package/build.mjs +92 -0
  4. package/dist/di.js +8 -0
  5. package/dist/di.js.map +7 -0
  6. package/dist/frontend/components/CommandPalette/CommandFooter.js +80 -0
  7. package/dist/frontend/components/CommandPalette/CommandFooter.js.map +7 -0
  8. package/dist/frontend/components/CommandPalette/CommandHeader.js +53 -0
  9. package/dist/frontend/components/CommandPalette/CommandHeader.js.map +7 -0
  10. package/dist/frontend/components/CommandPalette/CommandInput.js +29 -0
  11. package/dist/frontend/components/CommandPalette/CommandInput.js.map +7 -0
  12. package/dist/frontend/components/CommandPalette/CommandItem.js +92 -0
  13. package/dist/frontend/components/CommandPalette/CommandItem.js.map +7 -0
  14. package/dist/frontend/components/CommandPalette/CommandPalette.js +244 -0
  15. package/dist/frontend/components/CommandPalette/CommandPalette.js.map +7 -0
  16. package/dist/frontend/components/CommandPalette/CommandPaletteProvider.js +42 -0
  17. package/dist/frontend/components/CommandPalette/CommandPaletteProvider.js.map +7 -0
  18. package/dist/frontend/components/CommandPalette/CommandPaletteWrapper.js +18 -0
  19. package/dist/frontend/components/CommandPalette/CommandPaletteWrapper.js.map +7 -0
  20. package/dist/frontend/components/CommandPalette/DebugPanel.js +215 -0
  21. package/dist/frontend/components/CommandPalette/DebugPanel.js.map +7 -0
  22. package/dist/frontend/components/CommandPalette/MessageBubble.js +64 -0
  23. package/dist/frontend/components/CommandPalette/MessageBubble.js.map +7 -0
  24. package/dist/frontend/components/CommandPalette/ToolCallConfirmation.js +91 -0
  25. package/dist/frontend/components/CommandPalette/ToolCallConfirmation.js.map +7 -0
  26. package/dist/frontend/components/CommandPalette/ToolCallDisplay.js +47 -0
  27. package/dist/frontend/components/CommandPalette/ToolCallDisplay.js.map +7 -0
  28. package/dist/frontend/components/CommandPalette/ToolChatPage.js +74 -0
  29. package/dist/frontend/components/CommandPalette/ToolChatPage.js.map +7 -0
  30. package/dist/frontend/components/CommandPalette/index.js +28 -0
  31. package/dist/frontend/components/CommandPalette/index.js.map +7 -0
  32. package/dist/frontend/constants.js +41 -0
  33. package/dist/frontend/constants.js.map +7 -0
  34. package/dist/frontend/hooks/index.js +13 -0
  35. package/dist/frontend/hooks/index.js.map +7 -0
  36. package/dist/frontend/hooks/useCommandPalette.js +1094 -0
  37. package/dist/frontend/hooks/useCommandPalette.js.map +7 -0
  38. package/dist/frontend/hooks/useMcpTools.js +66 -0
  39. package/dist/frontend/hooks/useMcpTools.js.map +7 -0
  40. package/dist/frontend/hooks/usePageContext.js +48 -0
  41. package/dist/frontend/hooks/usePageContext.js.map +7 -0
  42. package/dist/frontend/hooks/useRecentActions.js +56 -0
  43. package/dist/frontend/hooks/useRecentActions.js.map +7 -0
  44. package/dist/frontend/hooks/useRecentTools.js +55 -0
  45. package/dist/frontend/hooks/useRecentTools.js.map +7 -0
  46. package/dist/frontend/index.js +35 -0
  47. package/dist/frontend/index.js.map +7 -0
  48. package/dist/frontend/types.js +1 -0
  49. package/dist/frontend/types.js.map +7 -0
  50. package/dist/frontend/utils/index.js +7 -0
  51. package/dist/frontend/utils/index.js.map +7 -0
  52. package/dist/frontend/utils/toolMatcher.js +95 -0
  53. package/dist/frontend/utils/toolMatcher.js.map +7 -0
  54. package/dist/index.js +57 -0
  55. package/dist/index.js.map +7 -0
  56. package/dist/modules/ai_assistant/acl.js +14 -0
  57. package/dist/modules/ai_assistant/acl.js.map +7 -0
  58. package/dist/modules/ai_assistant/api/chat/route.js +152 -0
  59. package/dist/modules/ai_assistant/api/chat/route.js.map +7 -0
  60. package/dist/modules/ai_assistant/api/health/route.js +27 -0
  61. package/dist/modules/ai_assistant/api/health/route.js.map +7 -0
  62. package/dist/modules/ai_assistant/api/route/route.js +123 -0
  63. package/dist/modules/ai_assistant/api/route/route.js.map +7 -0
  64. package/dist/modules/ai_assistant/api/settings/route.js +60 -0
  65. package/dist/modules/ai_assistant/api/settings/route.js.map +7 -0
  66. package/dist/modules/ai_assistant/api/tools/execute/route.js +58 -0
  67. package/dist/modules/ai_assistant/api/tools/execute/route.js.map +7 -0
  68. package/dist/modules/ai_assistant/api/tools/route.js +48 -0
  69. package/dist/modules/ai_assistant/api/tools/route.js.map +7 -0
  70. package/dist/modules/ai_assistant/backend/config/ai-assistant/page.js +10 -0
  71. package/dist/modules/ai_assistant/backend/config/ai-assistant/page.js.map +7 -0
  72. package/dist/modules/ai_assistant/backend/config/ai-assistant/page.meta.js +28 -0
  73. package/dist/modules/ai_assistant/backend/config/ai-assistant/page.meta.js.map +7 -0
  74. package/dist/modules/ai_assistant/cli.js +192 -0
  75. package/dist/modules/ai_assistant/cli.js.map +7 -0
  76. package/dist/modules/ai_assistant/di.js +11 -0
  77. package/dist/modules/ai_assistant/di.js.map +7 -0
  78. package/dist/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.js +257 -0
  79. package/dist/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.js.map +7 -0
  80. package/dist/modules/ai_assistant/index.js +13 -0
  81. package/dist/modules/ai_assistant/index.js.map +7 -0
  82. package/dist/modules/ai_assistant/lib/ai-sdk.js +13 -0
  83. package/dist/modules/ai_assistant/lib/ai-sdk.js.map +7 -0
  84. package/dist/modules/ai_assistant/lib/api-discovery-tools.js +249 -0
  85. package/dist/modules/ai_assistant/lib/api-discovery-tools.js.map +7 -0
  86. package/dist/modules/ai_assistant/lib/api-endpoint-index-config.js +177 -0
  87. package/dist/modules/ai_assistant/lib/api-endpoint-index-config.js.map +7 -0
  88. package/dist/modules/ai_assistant/lib/api-endpoint-index.js +210 -0
  89. package/dist/modules/ai_assistant/lib/api-endpoint-index.js.map +7 -0
  90. package/dist/modules/ai_assistant/lib/auth.js +87 -0
  91. package/dist/modules/ai_assistant/lib/auth.js.map +7 -0
  92. package/dist/modules/ai_assistant/lib/chat-config.js +117 -0
  93. package/dist/modules/ai_assistant/lib/chat-config.js.map +7 -0
  94. package/dist/modules/ai_assistant/lib/client-factory.js +60 -0
  95. package/dist/modules/ai_assistant/lib/client-factory.js.map +7 -0
  96. package/dist/modules/ai_assistant/lib/http-server.js +367 -0
  97. package/dist/modules/ai_assistant/lib/http-server.js.map +7 -0
  98. package/dist/modules/ai_assistant/lib/in-process-client.js +126 -0
  99. package/dist/modules/ai_assistant/lib/in-process-client.js.map +7 -0
  100. package/dist/modules/ai_assistant/lib/mcp-client.js +146 -0
  101. package/dist/modules/ai_assistant/lib/mcp-client.js.map +7 -0
  102. package/dist/modules/ai_assistant/lib/mcp-dev-server.js +283 -0
  103. package/dist/modules/ai_assistant/lib/mcp-dev-server.js.map +7 -0
  104. package/dist/modules/ai_assistant/lib/mcp-server-config.js +160 -0
  105. package/dist/modules/ai_assistant/lib/mcp-server-config.js.map +7 -0
  106. package/dist/modules/ai_assistant/lib/mcp-server.js +156 -0
  107. package/dist/modules/ai_assistant/lib/mcp-server.js.map +7 -0
  108. package/dist/modules/ai_assistant/lib/mcp-tool-adapter.js +44 -0
  109. package/dist/modules/ai_assistant/lib/mcp-tool-adapter.js.map +7 -0
  110. package/dist/modules/ai_assistant/lib/opencode-client.js +247 -0
  111. package/dist/modules/ai_assistant/lib/opencode-client.js.map +7 -0
  112. package/dist/modules/ai_assistant/lib/opencode-handlers.js +398 -0
  113. package/dist/modules/ai_assistant/lib/opencode-handlers.js.map +7 -0
  114. package/dist/modules/ai_assistant/lib/schema-utils.js +94 -0
  115. package/dist/modules/ai_assistant/lib/schema-utils.js.map +7 -0
  116. package/dist/modules/ai_assistant/lib/tool-executor.js +55 -0
  117. package/dist/modules/ai_assistant/lib/tool-executor.js.map +7 -0
  118. package/dist/modules/ai_assistant/lib/tool-index-config.js +125 -0
  119. package/dist/modules/ai_assistant/lib/tool-index-config.js.map +7 -0
  120. package/dist/modules/ai_assistant/lib/tool-loader.js +88 -0
  121. package/dist/modules/ai_assistant/lib/tool-loader.js.map +7 -0
  122. package/dist/modules/ai_assistant/lib/tool-registry.js +65 -0
  123. package/dist/modules/ai_assistant/lib/tool-registry.js.map +7 -0
  124. package/dist/modules/ai_assistant/lib/tool-search.js +192 -0
  125. package/dist/modules/ai_assistant/lib/tool-search.js.map +7 -0
  126. package/dist/modules/ai_assistant/lib/types.js +1 -0
  127. package/dist/modules/ai_assistant/lib/types.js.map +7 -0
  128. package/package.json +108 -0
  129. package/src/di.ts +11 -0
  130. package/src/frontend/components/CommandPalette/CommandFooter.tsx +113 -0
  131. package/src/frontend/components/CommandPalette/CommandHeader.tsx +76 -0
  132. package/src/frontend/components/CommandPalette/CommandInput.tsx +50 -0
  133. package/src/frontend/components/CommandPalette/CommandItem.tsx +111 -0
  134. package/src/frontend/components/CommandPalette/CommandPalette.tsx +276 -0
  135. package/src/frontend/components/CommandPalette/CommandPaletteProvider.tsx +60 -0
  136. package/src/frontend/components/CommandPalette/CommandPaletteWrapper.tsx +21 -0
  137. package/src/frontend/components/CommandPalette/DebugPanel.tsx +257 -0
  138. package/src/frontend/components/CommandPalette/MessageBubble.tsx +73 -0
  139. package/src/frontend/components/CommandPalette/ToolCallConfirmation.tsx +130 -0
  140. package/src/frontend/components/CommandPalette/ToolCallDisplay.tsx +57 -0
  141. package/src/frontend/components/CommandPalette/ToolChatPage.tsx +125 -0
  142. package/src/frontend/components/CommandPalette/index.ts +14 -0
  143. package/src/frontend/constants.ts +35 -0
  144. package/src/frontend/hooks/index.ts +5 -0
  145. package/src/frontend/hooks/useCommandPalette.ts +1389 -0
  146. package/src/frontend/hooks/useMcpTools.ts +73 -0
  147. package/src/frontend/hooks/usePageContext.ts +61 -0
  148. package/src/frontend/hooks/useRecentActions.ts +64 -0
  149. package/src/frontend/hooks/useRecentTools.ts +69 -0
  150. package/src/frontend/index.ts +39 -0
  151. package/src/frontend/types.ts +260 -0
  152. package/src/frontend/utils/index.ts +1 -0
  153. package/src/frontend/utils/toolMatcher.ts +127 -0
  154. package/src/index.ts +92 -0
  155. package/src/modules/ai_assistant/acl.ts +10 -0
  156. package/src/modules/ai_assistant/api/chat/route.ts +213 -0
  157. package/src/modules/ai_assistant/api/health/route.ts +30 -0
  158. package/src/modules/ai_assistant/api/route/route.ts +149 -0
  159. package/src/modules/ai_assistant/api/settings/route.ts +73 -0
  160. package/src/modules/ai_assistant/api/tools/execute/route.ts +71 -0
  161. package/src/modules/ai_assistant/api/tools/route.ts +57 -0
  162. package/src/modules/ai_assistant/backend/config/ai-assistant/page.meta.ts +26 -0
  163. package/src/modules/ai_assistant/backend/config/ai-assistant/page.tsx +12 -0
  164. package/src/modules/ai_assistant/cli.ts +233 -0
  165. package/src/modules/ai_assistant/di.ts +9 -0
  166. package/src/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.tsx +418 -0
  167. package/src/modules/ai_assistant/index.ts +11 -0
  168. package/src/modules/ai_assistant/lib/ai-sdk.ts +5 -0
  169. package/src/modules/ai_assistant/lib/api-discovery-tools.ts +334 -0
  170. package/src/modules/ai_assistant/lib/api-endpoint-index-config.ts +243 -0
  171. package/src/modules/ai_assistant/lib/api-endpoint-index.ts +381 -0
  172. package/src/modules/ai_assistant/lib/auth.ts +185 -0
  173. package/src/modules/ai_assistant/lib/chat-config.ts +152 -0
  174. package/src/modules/ai_assistant/lib/client-factory.ts +130 -0
  175. package/src/modules/ai_assistant/lib/http-server.ts +498 -0
  176. package/src/modules/ai_assistant/lib/in-process-client.ts +205 -0
  177. package/src/modules/ai_assistant/lib/mcp-client.ts +221 -0
  178. package/src/modules/ai_assistant/lib/mcp-dev-server.ts +373 -0
  179. package/src/modules/ai_assistant/lib/mcp-server-config.ts +287 -0
  180. package/src/modules/ai_assistant/lib/mcp-server.ts +214 -0
  181. package/src/modules/ai_assistant/lib/mcp-tool-adapter.ts +76 -0
  182. package/src/modules/ai_assistant/lib/opencode-client.ts +426 -0
  183. package/src/modules/ai_assistant/lib/opencode-handlers.ts +676 -0
  184. package/src/modules/ai_assistant/lib/schema-utils.ts +142 -0
  185. package/src/modules/ai_assistant/lib/tool-executor.ts +71 -0
  186. package/src/modules/ai_assistant/lib/tool-index-config.ts +178 -0
  187. package/src/modules/ai_assistant/lib/tool-loader.ts +149 -0
  188. package/src/modules/ai_assistant/lib/tool-registry.ts +114 -0
  189. package/src/modules/ai_assistant/lib/tool-search.ts +308 -0
  190. package/src/modules/ai_assistant/lib/types.ts +147 -0
  191. package/test-schema.ts +37 -0
  192. package/tsconfig.json +10 -0
  193. package/watch.mjs +6 -0
@@ -0,0 +1,73 @@
1
+ 'use client'
2
+
3
+ import { useState, useCallback, useEffect } from 'react'
4
+ import type { ToolInfo, ToolExecutionResult } from '../types'
5
+
6
+ export function useMcpTools() {
7
+ const [tools, setTools] = useState<ToolInfo[]>([])
8
+ const [isLoading, setIsLoading] = useState(false)
9
+ const [error, setError] = useState<string | null>(null)
10
+
11
+ const fetchTools = useCallback(async () => {
12
+ setIsLoading(true)
13
+ setError(null)
14
+ try {
15
+ const response = await fetch('/api/ai_assistant/tools')
16
+ if (!response.ok) {
17
+ throw new Error(`Failed to fetch tools: ${response.status}`)
18
+ }
19
+ const data = await response.json()
20
+ setTools(data.tools || [])
21
+ } catch (err) {
22
+ setError(err instanceof Error ? err.message : 'Failed to load tools')
23
+ setTools([])
24
+ } finally {
25
+ setIsLoading(false)
26
+ }
27
+ }, [])
28
+
29
+ const executeTool = useCallback(
30
+ async (toolName: string, args: Record<string, unknown> = {}): Promise<ToolExecutionResult> => {
31
+ try {
32
+ const response = await fetch('/api/ai_assistant/tools/execute', {
33
+ method: 'POST',
34
+ headers: { 'Content-Type': 'application/json' },
35
+ body: JSON.stringify({ toolName, args }),
36
+ })
37
+
38
+ const data = await response.json()
39
+
40
+ if (!response.ok) {
41
+ return {
42
+ success: false,
43
+ error: data.error || `Tool execution failed: ${response.status}`,
44
+ }
45
+ }
46
+
47
+ return {
48
+ success: true,
49
+ result: data.result,
50
+ }
51
+ } catch (err) {
52
+ return {
53
+ success: false,
54
+ error: err instanceof Error ? err.message : 'Tool execution failed',
55
+ }
56
+ }
57
+ },
58
+ []
59
+ )
60
+
61
+ // Fetch tools on mount
62
+ useEffect(() => {
63
+ fetchTools()
64
+ }, [fetchTools])
65
+
66
+ return {
67
+ tools,
68
+ isLoading,
69
+ error,
70
+ fetchTools,
71
+ executeTool,
72
+ }
73
+ }
@@ -0,0 +1,61 @@
1
+ 'use client'
2
+
3
+ import { useMemo } from 'react'
4
+ import { usePathname } from 'next/navigation'
5
+ import type { PageContext } from '../types'
6
+
7
+ interface UsePageContextOptions {
8
+ tenantId: string
9
+ organizationId: string | null
10
+ }
11
+
12
+ function extractModule(pathParts: string[]): string | null {
13
+ // Path structure: /backend/[module]/[entity]/[id]
14
+ // or /backend/[module]/[entity]
15
+ const backendIndex = pathParts.indexOf('backend')
16
+ if (backendIndex === -1 || backendIndex >= pathParts.length - 1) {
17
+ return null
18
+ }
19
+ return pathParts[backendIndex + 1] || null
20
+ }
21
+
22
+ function extractEntityType(pathParts: string[]): string | null {
23
+ const backendIndex = pathParts.indexOf('backend')
24
+ if (backendIndex === -1 || backendIndex >= pathParts.length - 2) {
25
+ return null
26
+ }
27
+ return pathParts[backendIndex + 2] || null
28
+ }
29
+
30
+ function extractRecordId(pathParts: string[]): string | null {
31
+ const backendIndex = pathParts.indexOf('backend')
32
+ if (backendIndex === -1 || backendIndex >= pathParts.length - 3) {
33
+ return null
34
+ }
35
+ const potentialId = pathParts[backendIndex + 3]
36
+ // Check if it looks like a UUID or ID
37
+ if (potentialId && /^[a-f0-9-]{36}$/i.test(potentialId)) {
38
+ return potentialId
39
+ }
40
+ return null
41
+ }
42
+
43
+ export function usePageContext(options: UsePageContextOptions): PageContext {
44
+ const pathname = usePathname()
45
+ const { tenantId, organizationId } = options
46
+
47
+ const pageContext = useMemo<PageContext>(() => {
48
+ const parts = pathname.split('/').filter(Boolean)
49
+
50
+ return {
51
+ path: pathname,
52
+ module: extractModule(parts),
53
+ entityType: extractEntityType(parts),
54
+ recordId: extractRecordId(parts),
55
+ tenantId,
56
+ organizationId,
57
+ }
58
+ }, [pathname, tenantId, organizationId])
59
+
60
+ return pageContext
61
+ }
@@ -0,0 +1,64 @@
1
+ 'use client'
2
+
3
+ import { useState, useCallback, useEffect } from 'react'
4
+ import type { RecentAction } from '../types'
5
+ import { RECENT_ACTIONS_KEY, MAX_RECENT_ACTIONS } from '../constants'
6
+
7
+ function loadRecentActions(): RecentAction[] {
8
+ if (typeof window === 'undefined') return []
9
+ try {
10
+ const stored = localStorage.getItem(RECENT_ACTIONS_KEY)
11
+ if (!stored) return []
12
+ const parsed = JSON.parse(stored)
13
+ if (!Array.isArray(parsed)) return []
14
+ return parsed.slice(0, MAX_RECENT_ACTIONS)
15
+ } catch {
16
+ return []
17
+ }
18
+ }
19
+
20
+ function saveRecentActions(actions: RecentAction[]): void {
21
+ if (typeof window === 'undefined') return
22
+ try {
23
+ localStorage.setItem(RECENT_ACTIONS_KEY, JSON.stringify(actions.slice(0, MAX_RECENT_ACTIONS)))
24
+ } catch {
25
+ // Ignore localStorage errors
26
+ }
27
+ }
28
+
29
+ export function useRecentActions() {
30
+ const [recentActions, setRecentActions] = useState<RecentAction[]>([])
31
+
32
+ // Load from localStorage on mount
33
+ useEffect(() => {
34
+ setRecentActions(loadRecentActions())
35
+ }, [])
36
+
37
+ const addRecentAction = useCallback((action: Omit<RecentAction, 'id' | 'timestamp'>) => {
38
+ setRecentActions((prev) => {
39
+ const newAction: RecentAction = {
40
+ ...action,
41
+ id: crypto.randomUUID(),
42
+ timestamp: Date.now(),
43
+ }
44
+ // Remove duplicates by toolName, add new at start
45
+ const filtered = prev.filter((a) => a.toolName !== action.toolName)
46
+ const updated = [newAction, ...filtered].slice(0, MAX_RECENT_ACTIONS)
47
+ saveRecentActions(updated)
48
+ return updated
49
+ })
50
+ }, [])
51
+
52
+ const clearRecentActions = useCallback(() => {
53
+ setRecentActions([])
54
+ if (typeof window !== 'undefined') {
55
+ localStorage.removeItem(RECENT_ACTIONS_KEY)
56
+ }
57
+ }, [])
58
+
59
+ return {
60
+ recentActions,
61
+ addRecentAction,
62
+ clearRecentActions,
63
+ }
64
+ }
@@ -0,0 +1,69 @@
1
+ 'use client'
2
+
3
+ import { useState, useCallback, useEffect, useMemo } from 'react'
4
+ import type { ToolInfo } from '../types'
5
+
6
+ const RECENT_TOOLS_KEY = 'om:command-palette:recent-tools'
7
+ const MAX_RECENT_TOOLS = 5
8
+
9
+ function loadRecentToolIds(): string[] {
10
+ if (typeof window === 'undefined') return []
11
+ try {
12
+ const stored = localStorage.getItem(RECENT_TOOLS_KEY)
13
+ if (!stored) return []
14
+ const parsed = JSON.parse(stored)
15
+ if (!Array.isArray(parsed)) return []
16
+ return parsed.slice(0, MAX_RECENT_TOOLS)
17
+ } catch {
18
+ return []
19
+ }
20
+ }
21
+
22
+ function saveRecentToolIds(toolIds: string[]): void {
23
+ if (typeof window === 'undefined') return
24
+ try {
25
+ localStorage.setItem(RECENT_TOOLS_KEY, JSON.stringify(toolIds.slice(0, MAX_RECENT_TOOLS)))
26
+ } catch {
27
+ // Ignore localStorage errors
28
+ }
29
+ }
30
+
31
+ export function useRecentTools(allTools: ToolInfo[]) {
32
+ const [recentIds, setRecentIds] = useState<string[]>([])
33
+
34
+ // Load from localStorage on mount
35
+ useEffect(() => {
36
+ setRecentIds(loadRecentToolIds())
37
+ }, [])
38
+
39
+ const saveRecentTool = useCallback((toolName: string) => {
40
+ setRecentIds((prev) => {
41
+ // Add new tool at start, remove duplicates
42
+ const updated = [toolName, ...prev.filter((t) => t !== toolName)].slice(0, MAX_RECENT_TOOLS)
43
+ saveRecentToolIds(updated)
44
+ return updated
45
+ })
46
+ }, [])
47
+
48
+ const clearRecentTools = useCallback(() => {
49
+ setRecentIds([])
50
+ if (typeof window !== 'undefined') {
51
+ localStorage.removeItem(RECENT_TOOLS_KEY)
52
+ }
53
+ }, [])
54
+
55
+ // Map recent tool IDs to actual tool objects
56
+ const recentTools = useMemo(
57
+ () =>
58
+ recentIds
59
+ .map((id) => allTools.find((t) => t.name === id))
60
+ .filter((t): t is ToolInfo => t !== undefined),
61
+ [recentIds, allTools]
62
+ )
63
+
64
+ return {
65
+ recentTools,
66
+ saveRecentTool,
67
+ clearRecentTools,
68
+ }
69
+ }
@@ -0,0 +1,39 @@
1
+ // Components
2
+ export {
3
+ CommandPalette,
4
+ CommandPaletteProvider,
5
+ CommandPaletteWrapper,
6
+ useCommandPaletteContext,
7
+ } from './components/CommandPalette'
8
+
9
+ // Hooks
10
+ export {
11
+ useCommandPalette,
12
+ useMcpTools,
13
+ usePageContext,
14
+ useRecentActions,
15
+ } from './hooks'
16
+
17
+ // Types
18
+ export type {
19
+ CommandPaletteMode,
20
+ PageContext,
21
+ SelectedEntity,
22
+ ToolInfo,
23
+ ToolExecutionResult,
24
+ RecentAction,
25
+ ToolCall,
26
+ ChatMessage,
27
+ CommandPaletteState,
28
+ CommandPaletteContextValue,
29
+ } from './types'
30
+
31
+ // Utils
32
+ export { filterTools, groupToolsByModule, humanizeToolName } from './utils'
33
+
34
+ // Constants
35
+ export {
36
+ COMMAND_PALETTE_SHORTCUT,
37
+ RECENT_ACTIONS_KEY,
38
+ MAX_RECENT_ACTIONS,
39
+ } from './constants'
@@ -0,0 +1,260 @@
1
+ // Phase-based state for intelligent routing
2
+ export type PalettePhase =
3
+ | 'idle' // Empty, waiting for input
4
+ | 'routing' // Fast model analyzing intent
5
+ | 'chatting' // Smart model conversation for tool params
6
+ | 'confirming' // Waiting for user to approve tool call
7
+ | 'executing' // Tool running
8
+
9
+ // Page-based navigation for Raycast-style interface (deprecated, use PalettePhase)
10
+ export type CommandPalettePage = 'home' | 'tool-chat'
11
+
12
+ // Connection status for MCP servers
13
+ export type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'error'
14
+
15
+ // Legacy mode type (deprecated, kept for compatibility)
16
+ export type CommandPaletteMode = 'commands' | 'chat'
17
+
18
+ export interface PageContext {
19
+ path: string
20
+ module: string | null
21
+ entityType: string | null
22
+ recordId: string | null
23
+ tenantId: string
24
+ organizationId: string | null
25
+ }
26
+
27
+ export interface SelectedEntity {
28
+ entityType: string
29
+ recordId: string
30
+ displayName: string
31
+ }
32
+
33
+ export interface ToolInfo {
34
+ name: string
35
+ description: string
36
+ inputSchema: Record<string, unknown>
37
+ module?: string
38
+ }
39
+
40
+ export interface ToolExecutionResult {
41
+ success: boolean
42
+ result?: unknown
43
+ error?: string
44
+ }
45
+
46
+ export interface RecentAction {
47
+ id: string
48
+ toolName: string
49
+ displayName: string
50
+ timestamp: number
51
+ args?: Record<string, unknown>
52
+ }
53
+
54
+ // Tool call with confirmation support
55
+ export interface ToolCall {
56
+ id: string
57
+ toolName: string
58
+ args: Record<string, unknown>
59
+ status: 'pending' | 'running' | 'completed' | 'error'
60
+ result?: unknown
61
+ error?: string
62
+ }
63
+
64
+ // Pending tool call awaiting user confirmation
65
+ export interface PendingToolCall {
66
+ id: string
67
+ toolName: string
68
+ args: Record<string, unknown>
69
+ status: 'pending' | 'approved' | 'rejected' | 'executing' | 'completed' | 'error'
70
+ result?: unknown
71
+ error?: string
72
+ }
73
+
74
+ export interface ChatMessage {
75
+ id: string
76
+ role: 'user' | 'assistant' | 'system'
77
+ content: string
78
+ createdAt?: Date
79
+ // Tool call info if this message contains a tool call
80
+ toolCalls?: PendingToolCall[]
81
+ }
82
+
83
+ // New phase-based state for intelligent routing
84
+ export interface CommandPaletteState {
85
+ isOpen: boolean
86
+ phase: PalettePhase
87
+ inputValue: string
88
+ selectedIndex: number
89
+ isLoading: boolean
90
+ isStreaming: boolean
91
+ connectionStatus: ConnectionStatus
92
+ // Legacy fields for backwards compatibility
93
+ page: CommandPalettePage
94
+ mode: CommandPaletteMode
95
+ }
96
+
97
+ export interface CommandPaletteContextValue {
98
+ state: CommandPaletteState
99
+ isThinking: boolean
100
+ isSessionAuthorized: boolean
101
+ pageContext: PageContext | null
102
+ selectedEntities: SelectedEntity[]
103
+ tools: ToolInfo[]
104
+ filteredTools: ToolInfo[]
105
+ recentActions: RecentAction[]
106
+ recentTools: ToolInfo[]
107
+ messages: ChatMessage[]
108
+ pendingToolCalls: PendingToolCall[]
109
+ selectedTool: ToolInfo | null
110
+ initialContext: {
111
+ tenantId: string | null
112
+ organizationId: string | null
113
+ userId: string
114
+ isSuperAdmin: boolean
115
+ features: string[]
116
+ } | null
117
+ availableEntities: Array<{ entityId: string; enabled: boolean }> | null
118
+
119
+ // Navigation actions
120
+ open: () => void
121
+ close: () => void
122
+ setInputValue: (value: string) => void
123
+ setSelectedIndex: (index: number) => void
124
+
125
+ // Intelligent routing - submit natural language query
126
+ handleSubmit: (query: string) => Promise<void>
127
+ reset: () => void
128
+
129
+ // Page navigation (legacy, kept for compatibility)
130
+ goToToolChat: (tool: ToolInfo) => void
131
+ goBack: () => void
132
+
133
+ // Tool execution
134
+ executeTool: (toolName: string, args?: Record<string, unknown>) => Promise<ToolExecutionResult>
135
+ approveToolCall: (toolCallId: string) => Promise<void>
136
+ rejectToolCall: (toolCallId: string) => void
137
+
138
+ // Chat actions
139
+ sendMessage: (content: string) => Promise<void>
140
+ sendAgenticMessage: (content: string) => Promise<void>
141
+ clearMessages: () => void
142
+
143
+ // Legacy compatibility
144
+ setMode: (mode: CommandPaletteMode) => void
145
+ setIsOpen: (isOpen: boolean) => void
146
+
147
+ // Debug mode
148
+ debugEvents: DebugEvent[]
149
+ showDebug: boolean
150
+ setShowDebug: (show: boolean) => void
151
+ clearDebugEvents: () => void
152
+
153
+ // OpenCode question handling
154
+ pendingQuestion: OpenCodeQuestion | null
155
+ answerQuestion: (answer: number) => Promise<void>
156
+ }
157
+
158
+ export interface ChatApiRequest {
159
+ messages: ChatMessage[]
160
+ context: PageContext | null
161
+ mode?: 'default' | 'tool-assist' | 'tool-assist-confirm'
162
+ toolName?: string // For tool-specific chat sessions
163
+ }
164
+
165
+ // Routing API response
166
+ export interface RouteResult {
167
+ intent: 'tool' | 'general_chat'
168
+ toolName?: string
169
+ confidence: number
170
+ reasoning: string
171
+ }
172
+
173
+ export interface ToolsApiResponse {
174
+ tools: ToolInfo[]
175
+ }
176
+
177
+ export interface ToolExecuteRequest {
178
+ toolName: string
179
+ args: Record<string, unknown>
180
+ }
181
+
182
+ export interface ToolExecuteResponse {
183
+ success: boolean
184
+ result?: unknown
185
+ error?: string
186
+ }
187
+
188
+ // Stream event types for tool-assist-confirm mode
189
+ export type StreamEventType = 'text' | 'tool-call' | 'tool-result' | 'error' | 'done'
190
+
191
+ export interface StreamEvent {
192
+ type: StreamEventType
193
+ content?: string
194
+ toolCall?: {
195
+ id: string
196
+ toolName: string
197
+ args: Record<string, unknown>
198
+ }
199
+ toolResult?: {
200
+ id: string
201
+ result: unknown
202
+ }
203
+ error?: string
204
+ }
205
+
206
+ // Debug event types for debugging the AI chat
207
+ export type DebugEventType =
208
+ | 'thinking'
209
+ | 'tool-call'
210
+ | 'tool-result'
211
+ | 'text'
212
+ | 'error'
213
+ | 'done'
214
+ | 'message'
215
+ | 'connection'
216
+ | 'metadata'
217
+ | 'debug'
218
+ | 'question'
219
+ | 'session-authorized'
220
+
221
+ export interface DebugEvent {
222
+ id: string
223
+ timestamp: Date
224
+ type: DebugEventType
225
+ data: unknown
226
+ }
227
+
228
+ // Question option from OpenCode
229
+ export interface OpenCodeQuestionOption {
230
+ label: string
231
+ description: string
232
+ }
233
+
234
+ // Question from OpenCode requiring user confirmation
235
+ export interface OpenCodeQuestion {
236
+ id: string
237
+ sessionID: string
238
+ questions: Array<{
239
+ question: string
240
+ header: string
241
+ options: OpenCodeQuestionOption[]
242
+ }>
243
+ tool: {
244
+ messageID: string
245
+ callID: string
246
+ }
247
+ }
248
+
249
+ // SSE event types from chat API (OpenCode integration)
250
+ export type ChatSSEEvent =
251
+ | { type: 'thinking' }
252
+ | { type: 'text'; content: string }
253
+ | { type: 'tool-call'; id: string; toolName: string; args: unknown }
254
+ | { type: 'tool-result'; id: string; result: unknown }
255
+ | { type: 'question'; question: OpenCodeQuestion }
256
+ | { type: 'metadata'; model?: string; provider?: string; tokens?: { input: number; output: number }; timing?: { created: number; completed?: number }; durationMs?: number }
257
+ | { type: 'debug'; partType: string; data: unknown }
258
+ | { type: 'done'; sessionId?: string }
259
+ | { type: 'error'; error: string }
260
+ | { type: 'session-authorized'; sessionToken?: string }
@@ -0,0 +1 @@
1
+ export { filterTools, groupToolsByModule, humanizeToolName } from './toolMatcher'
@@ -0,0 +1,127 @@
1
+ import type { ToolInfo } from '../types'
2
+
3
+ function normalizeString(str: string): string {
4
+ return str.toLowerCase().replace(/[._-]/g, ' ')
5
+ }
6
+
7
+ function fuzzyMatch(query: string, target: string): boolean {
8
+ const normalizedQuery = normalizeString(query)
9
+ const normalizedTarget = normalizeString(target)
10
+
11
+ // Check if all query chars appear in order in target
12
+ let queryIndex = 0
13
+ for (let i = 0; i < normalizedTarget.length && queryIndex < normalizedQuery.length; i++) {
14
+ if (normalizedTarget[i] === normalizedQuery[queryIndex]) {
15
+ queryIndex++
16
+ }
17
+ }
18
+ return queryIndex === normalizedQuery.length
19
+ }
20
+
21
+ function scoreMatch(query: string, tool: ToolInfo): number {
22
+ const normalizedQuery = normalizeString(query)
23
+ const normalizedName = normalizeString(tool.name)
24
+ const normalizedDesc = normalizeString(tool.description)
25
+
26
+ let score = 0
27
+
28
+ // Exact match in name = highest score
29
+ if (normalizedName.includes(normalizedQuery)) {
30
+ score += 100
31
+ }
32
+
33
+ // Starts with query
34
+ if (normalizedName.startsWith(normalizedQuery)) {
35
+ score += 50
36
+ }
37
+
38
+ // Word boundary match
39
+ const nameWords = normalizedName.split(' ')
40
+ if (nameWords.some((word) => word.startsWith(normalizedQuery))) {
41
+ score += 30
42
+ }
43
+
44
+ // Fuzzy match in name
45
+ if (fuzzyMatch(normalizedQuery, normalizedName)) {
46
+ score += 20
47
+ }
48
+
49
+ // Match in description
50
+ if (normalizedDesc.includes(normalizedQuery)) {
51
+ score += 10
52
+ }
53
+
54
+ return score
55
+ }
56
+
57
+ export function filterTools(tools: ToolInfo[], query: string): ToolInfo[] {
58
+ if (!query.trim()) {
59
+ return tools
60
+ }
61
+
62
+ const normalizedQuery = query.trim().toLowerCase()
63
+
64
+ // Score and filter tools
65
+ const scoredTools = tools
66
+ .map((tool) => ({
67
+ tool,
68
+ score: scoreMatch(normalizedQuery, tool),
69
+ }))
70
+ .filter(({ score }) => score > 0)
71
+ .sort((a, b) => b.score - a.score)
72
+
73
+ return scoredTools.map(({ tool }) => tool)
74
+ }
75
+
76
+ export function groupToolsByModule(tools: ToolInfo[]): Map<string, ToolInfo[]> {
77
+ const grouped = new Map<string, ToolInfo[]>()
78
+
79
+ for (const tool of tools) {
80
+ const module = tool.module || extractModuleFromName(tool.name)
81
+ const existing = grouped.get(module) || []
82
+ existing.push(tool)
83
+ grouped.set(module, existing)
84
+ }
85
+
86
+ return grouped
87
+ }
88
+
89
+ function extractModuleFromName(name: string): string {
90
+ const parts = name.split('.')
91
+ return parts[0] || 'other'
92
+ }
93
+
94
+ export function humanizeToolName(name: string): string {
95
+ // customers.people.create -> Create Person
96
+ const parts = name.split('.')
97
+ if (parts.length < 2) return name
98
+
99
+ const action = parts[parts.length - 1]
100
+ const resource = parts[parts.length - 2]
101
+
102
+ const humanAction = capitalize(action)
103
+ const humanResource = singularize(humanize(resource))
104
+
105
+ return `${humanAction} ${humanResource}`
106
+ }
107
+
108
+ function capitalize(str: string): string {
109
+ return str.charAt(0).toUpperCase() + str.slice(1)
110
+ }
111
+
112
+ function humanize(str: string): string {
113
+ return str.replace(/[_-]/g, ' ').replace(/([a-z])([A-Z])/g, '$1 $2')
114
+ }
115
+
116
+ function singularize(str: string): string {
117
+ if (str.endsWith('ies')) {
118
+ return str.slice(0, -3) + 'y'
119
+ }
120
+ if (str.endsWith('es') && !str.endsWith('ses')) {
121
+ return str.slice(0, -2)
122
+ }
123
+ if (str.endsWith('s') && !str.endsWith('ss')) {
124
+ return str.slice(0, -1)
125
+ }
126
+ return str
127
+ }