@open-mercato/ai-assistant 0.5.1-develop.2683.4878a05b8e → 0.5.1-develop.2694.732417c5ec
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
ai-assistant built successfully
|
|
1
|
+
[build:ai-assistant] found 90 entry points
|
|
2
|
+
[build:ai-assistant] built successfully
|
package/build.mjs
CHANGED
|
@@ -1,103 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { glob } from 'glob'
|
|
3
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync, copyFileSync } from 'node:fs'
|
|
4
|
-
import { dirname, join, relative } from 'node:path'
|
|
1
|
+
import { dirname } from 'node:path'
|
|
5
2
|
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { buildPackage } from '../../scripts/build-package.mjs'
|
|
6
4
|
|
|
7
|
-
const
|
|
5
|
+
const packageDir = dirname(fileURLToPath(import.meta.url))
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
absolute: true,
|
|
7
|
+
await buildPackage(packageDir, {
|
|
8
|
+
name: 'ai-assistant',
|
|
9
|
+
copyJson: true,
|
|
13
10
|
})
|
|
14
|
-
|
|
15
|
-
if (entryPoints.length === 0) {
|
|
16
|
-
console.error('No entry points found!')
|
|
17
|
-
process.exit(1)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
console.log(`Found ${entryPoints.length} entry points`)
|
|
21
|
-
|
|
22
|
-
// Plugin to add .js extension to relative imports
|
|
23
|
-
const addJsExtension = {
|
|
24
|
-
name: 'add-js-extension',
|
|
25
|
-
setup(build) {
|
|
26
|
-
build.onEnd(async (result) => {
|
|
27
|
-
if (result.errors.length > 0) return
|
|
28
|
-
const outputFiles = await glob('dist/**/*.js', { cwd: __dirname, absolute: true })
|
|
29
|
-
for (const file of outputFiles) {
|
|
30
|
-
const fileDir = dirname(file)
|
|
31
|
-
let content = readFileSync(file, 'utf-8')
|
|
32
|
-
// Add .js to relative imports that don't have an extension
|
|
33
|
-
content = content.replace(
|
|
34
|
-
/from\s+["'](\.[^"']+)["']/g,
|
|
35
|
-
(match, path) => {
|
|
36
|
-
if (path.endsWith('.js') || path.endsWith('.json')) return match
|
|
37
|
-
// Check if it's a directory with index.js
|
|
38
|
-
const resolvedPath = join(fileDir, path)
|
|
39
|
-
if (existsSync(resolvedPath) && existsSync(join(resolvedPath, 'index.js'))) {
|
|
40
|
-
return `from "${path}/index.js"`
|
|
41
|
-
}
|
|
42
|
-
return `from "${path}.js"`
|
|
43
|
-
}
|
|
44
|
-
)
|
|
45
|
-
content = content.replace(
|
|
46
|
-
/import\s*\(\s*["'](\.[^"']+)["']\s*\)/g,
|
|
47
|
-
(match, path) => {
|
|
48
|
-
if (path.endsWith('.js') || path.endsWith('.json')) return match
|
|
49
|
-
// Check if it's a directory with index.js
|
|
50
|
-
const resolvedPath = join(fileDir, path)
|
|
51
|
-
if (existsSync(resolvedPath) && existsSync(join(resolvedPath, 'index.js'))) {
|
|
52
|
-
return `import("${path}/index.js")`
|
|
53
|
-
}
|
|
54
|
-
return `import("${path}.js")`
|
|
55
|
-
}
|
|
56
|
-
)
|
|
57
|
-
// Handle side-effect imports: import "./path" (no from clause)
|
|
58
|
-
content = content.replace(
|
|
59
|
-
/import\s+["'](\.[^"']+)["'];/g,
|
|
60
|
-
(match, path) => {
|
|
61
|
-
if (path.endsWith('.js') || path.endsWith('.json')) return match
|
|
62
|
-
// Check if it's a directory with index.js
|
|
63
|
-
const resolvedPath = join(fileDir, path)
|
|
64
|
-
if (existsSync(resolvedPath) && existsSync(join(resolvedPath, 'index.js'))) {
|
|
65
|
-
return `import "${path}/index.js";`
|
|
66
|
-
}
|
|
67
|
-
return `import "${path}.js";`
|
|
68
|
-
}
|
|
69
|
-
)
|
|
70
|
-
writeFileSync(file, content)
|
|
71
|
-
}
|
|
72
|
-
})
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const outdir = join(__dirname, 'dist')
|
|
77
|
-
|
|
78
|
-
await esbuild.build({
|
|
79
|
-
entryPoints,
|
|
80
|
-
outdir,
|
|
81
|
-
outbase: join(__dirname, 'src'),
|
|
82
|
-
format: 'esm',
|
|
83
|
-
platform: 'node',
|
|
84
|
-
target: 'node18',
|
|
85
|
-
sourcemap: true,
|
|
86
|
-
jsx: 'automatic',
|
|
87
|
-
plugins: [addJsExtension],
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
// Copy JSON files from src to dist
|
|
91
|
-
const jsonFiles = await glob('src/**/*.json', {
|
|
92
|
-
cwd: __dirname,
|
|
93
|
-
ignore: ['**/node_modules/**'],
|
|
94
|
-
absolute: true,
|
|
95
|
-
})
|
|
96
|
-
for (const jsonFile of jsonFiles) {
|
|
97
|
-
const relativePath = relative(join(__dirname, 'src'), jsonFile)
|
|
98
|
-
const destPath = join(outdir, relativePath)
|
|
99
|
-
mkdirSync(dirname(destPath), { recursive: true })
|
|
100
|
-
copyFileSync(jsonFile, destPath)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
console.log('ai-assistant built successfully')
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/ai_assistant/lib/auth.ts"],
|
|
4
|
-
"sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\n\n/**\n * Successful authentication result.\n */\nexport type McpAuthSuccess = {\n success: true\n keyId: string\n keyName: string\n tenantId: string | null\n organizationId: string | null\n userId: string\n features: string[]\n isSuperAdmin: boolean\n}\n\n/**\n * Failed authentication result.\n */\nexport type McpAuthFailure = {\n success: false\n error: string\n}\n\n/**\n * Result from MCP authentication.\n */\nexport type McpAuthResult = McpAuthSuccess | McpAuthFailure\n\n/**\n * Authenticate an MCP request using an API key.\n *\n * This function validates the API key secret and loads the associated\n * ACL (features, organizations, super admin status) from the key's roles.\n *\n * @param apiKeySecret - The full API key secret (e.g., 'omk_xxxx.yyyy...')\n * @param container - Awilix DI container with 'em' and 'rbacService'\n * @returns Authentication result with user context or error\n */\nexport async function authenticateMcpRequest(\n apiKeySecret: string,\n container: AwilixContainer\n): Promise<McpAuthResult> {\n if (!apiKeySecret || typeof apiKeySecret !== 'string') {\n return { success: false, error: 'API key is required' }\n }\n\n const trimmedSecret = apiKeySecret.trim()\n if (!trimmedSecret) {\n return { success: false, error: 'API key is required' }\n }\n\n if (!trimmedSecret.startsWith('omk_')) {\n return { success: false, error: 'Invalid API key format' }\n }\n\n try {\n const em = container.resolve('em') as EntityManager\n\n const { findApiKeyBySecret } = await import(\n '@open-mercato/core/modules/api_keys/services/apiKeyService'\n )\n\n const apiKey = await findApiKeyBySecret(em, trimmedSecret)\n\n if (!apiKey) {\n return { success: false, error: 'Invalid or expired API key' }\n }\n\n const userId = `api_key:${apiKey.id}`\n\n const rbacService = container.resolve('rbacService') as {\n loadAcl: (\n userId: string,\n scope: { tenantId: string | null; organizationId: string | null }\n ) => Promise<{\n isSuperAdmin: boolean\n features: string[]\n organizations: string[] | null\n }>\n }\n\n const acl = await rbacService.loadAcl(userId, {\n tenantId: apiKey.tenantId ?? null,\n organizationId: apiKey.organizationId ?? null,\n })\n\n try {\n apiKey.lastUsedAt = new Date()\n await em.
|
|
5
|
-
"mappings": "AAyCA,eAAsB,uBACpB,cACA,WACwB;AACxB,MAAI,CAAC,gBAAgB,OAAO,iBAAiB,UAAU;AACrD,WAAO,EAAE,SAAS,OAAO,OAAO,sBAAsB;AAAA,EACxD;AAEA,QAAM,gBAAgB,aAAa,KAAK;AACxC,MAAI,CAAC,eAAe;AAClB,WAAO,EAAE,SAAS,OAAO,OAAO,sBAAsB;AAAA,EACxD;AAEA,MAAI,CAAC,cAAc,WAAW,MAAM,GAAG;AACrC,WAAO,EAAE,SAAS,OAAO,OAAO,yBAAyB;AAAA,EAC3D;AAEA,MAAI;AACF,UAAM,KAAK,UAAU,QAAQ,IAAI;AAEjC,UAAM,EAAE,mBAAmB,IAAI,MAAM,OACnC,4DACF;AAEA,UAAM,SAAS,MAAM,mBAAmB,IAAI,aAAa;AAEzD,QAAI,CAAC,QAAQ;AACX,aAAO,EAAE,SAAS,OAAO,OAAO,6BAA6B;AAAA,IAC/D;AAEA,UAAM,SAAS,WAAW,OAAO,EAAE;AAEnC,UAAM,cAAc,UAAU,QAAQ,aAAa;AAWnD,UAAM,MAAM,MAAM,YAAY,QAAQ,QAAQ;AAAA,MAC5C,UAAU,OAAO,YAAY;AAAA,MAC7B,gBAAgB,OAAO,kBAAkB;AAAA,IAC3C,CAAC;AAED,QAAI;AACF,aAAO,aAAa,oBAAI,KAAK;AAC7B,YAAM,GAAG,
|
|
4
|
+
"sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\n\n/**\n * Successful authentication result.\n */\nexport type McpAuthSuccess = {\n success: true\n keyId: string\n keyName: string\n tenantId: string | null\n organizationId: string | null\n userId: string\n features: string[]\n isSuperAdmin: boolean\n}\n\n/**\n * Failed authentication result.\n */\nexport type McpAuthFailure = {\n success: false\n error: string\n}\n\n/**\n * Result from MCP authentication.\n */\nexport type McpAuthResult = McpAuthSuccess | McpAuthFailure\n\n/**\n * Authenticate an MCP request using an API key.\n *\n * This function validates the API key secret and loads the associated\n * ACL (features, organizations, super admin status) from the key's roles.\n *\n * @param apiKeySecret - The full API key secret (e.g., 'omk_xxxx.yyyy...')\n * @param container - Awilix DI container with 'em' and 'rbacService'\n * @returns Authentication result with user context or error\n */\nexport async function authenticateMcpRequest(\n apiKeySecret: string,\n container: AwilixContainer\n): Promise<McpAuthResult> {\n if (!apiKeySecret || typeof apiKeySecret !== 'string') {\n return { success: false, error: 'API key is required' }\n }\n\n const trimmedSecret = apiKeySecret.trim()\n if (!trimmedSecret) {\n return { success: false, error: 'API key is required' }\n }\n\n if (!trimmedSecret.startsWith('omk_')) {\n return { success: false, error: 'Invalid API key format' }\n }\n\n try {\n const em = container.resolve('em') as EntityManager\n\n const { findApiKeyBySecret } = await import(\n '@open-mercato/core/modules/api_keys/services/apiKeyService'\n )\n\n const apiKey = await findApiKeyBySecret(em, trimmedSecret)\n\n if (!apiKey) {\n return { success: false, error: 'Invalid or expired API key' }\n }\n\n const userId = `api_key:${apiKey.id}`\n\n const rbacService = container.resolve('rbacService') as {\n loadAcl: (\n userId: string,\n scope: { tenantId: string | null; organizationId: string | null }\n ) => Promise<{\n isSuperAdmin: boolean\n features: string[]\n organizations: string[] | null\n }>\n }\n\n const acl = await rbacService.loadAcl(userId, {\n tenantId: apiKey.tenantId ?? null,\n organizationId: apiKey.organizationId ?? null,\n })\n\n try {\n apiKey.lastUsedAt = new Date()\n await em.persist(apiKey).flush()\n } catch {\n // Best-effort update; ignore write failures\n }\n\n return {\n success: true,\n keyId: apiKey.id,\n keyName: apiKey.name,\n tenantId: apiKey.tenantId ?? null,\n organizationId: apiKey.organizationId ?? null,\n userId,\n features: acl.features,\n isSuperAdmin: acl.isSuperAdmin,\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n console.error('[MCP Auth] Authentication failed:', message)\n return { success: false, error: 'Authentication failed' }\n }\n}\n\n/**\n * Check if user has the required features for a resource.\n *\n * Supports:\n * - Super admin bypass (always returns true)\n * - Direct feature match (e.g., 'customers.view')\n * - Global wildcard ('*' grants all features)\n * - Prefix wildcard (e.g., 'customers.*' grants 'customers.people.view')\n *\n * @param requiredFeatures - List of features required for access\n * @param userFeatures - List of features the user has\n * @param isSuperAdmin - Whether the user is a super admin\n * @param rbacService - Optional RbacService to delegate feature matching\n * @returns True if user has access\n */\nexport function hasRequiredFeatures(\n requiredFeatures: string[] | undefined,\n userFeatures: string[],\n isSuperAdmin: boolean,\n rbacService?: RbacService\n): boolean {\n if (isSuperAdmin) return true\n if (!requiredFeatures?.length) return true\n\n // Delegate to RbacService if provided\n if (rbacService) {\n return rbacService.hasAllFeatures(requiredFeatures, userFeatures)\n }\n\n // Fallback for cases without rbacService (keeps backward compatibility)\n return requiredFeatures.every((required) => {\n if (userFeatures.includes(required)) return true\n if (userFeatures.includes('*')) return true\n\n // Check wildcard patterns (e.g., 'customers.*' grants 'customers.people.view')\n return userFeatures.some((feature) => {\n if (feature.endsWith('.*')) {\n const prefix = feature.slice(0, -2)\n return required.startsWith(prefix + '.')\n }\n return false\n })\n })\n}\n\n/**\n * Extract API key from HTTP request headers.\n *\n * Supports two header formats:\n * - x-api-key: <secret>\n * - Authorization: ApiKey <secret>\n *\n * @param headers - Request headers (Map, Headers, or plain object)\n * @returns The API key secret or null if not found\n */\nexport function extractApiKeyFromHeaders(\n headers: Headers | Map<string, string> | Record<string, string | undefined>\n): string | null {\n const getHeader = (name: string): string | null => {\n if (headers instanceof Headers) {\n return headers.get(name)\n }\n if (headers instanceof Map) {\n return headers.get(name) ?? null\n }\n const value = headers[name] ?? headers[name.toLowerCase()]\n return typeof value === 'string' ? value : null\n }\n\n const xApiKey = getHeader('x-api-key')?.trim()\n if (xApiKey) {\n return xApiKey\n }\n\n const authHeader = getHeader('authorization')?.trim()\n if (authHeader && authHeader.toLowerCase().startsWith('apikey ')) {\n return authHeader.slice(7).trim()\n }\n\n return null\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/jest.config.cjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/** @type {import('jest').Config} */
|
|
2
2
|
module.exports = {
|
|
3
|
-
preset: 'ts-jest',
|
|
4
3
|
testEnvironment: 'node',
|
|
5
4
|
watchman: false,
|
|
6
5
|
rootDir: '.',
|
|
@@ -10,8 +9,8 @@ module.exports = {
|
|
|
10
9
|
'^@open-mercato/shared/(.*)$': '<rootDir>/../shared/src/$1',
|
|
11
10
|
},
|
|
12
11
|
transform: {
|
|
13
|
-
'^.+\\.
|
|
14
|
-
'
|
|
12
|
+
'^.+\\.(t|j)sx?$': [
|
|
13
|
+
'<rootDir>/../../scripts/jest-mikroorm-transformer.cjs',
|
|
15
14
|
{
|
|
16
15
|
tsconfig: {
|
|
17
16
|
jsx: 'react-jsx',
|
|
@@ -19,6 +18,9 @@ module.exports = {
|
|
|
19
18
|
},
|
|
20
19
|
],
|
|
21
20
|
},
|
|
21
|
+
transformIgnorePatterns: [
|
|
22
|
+
'node_modules/(?!(@mikro-orm)/)',
|
|
23
|
+
],
|
|
22
24
|
testMatch: ['<rootDir>/src/**/__tests__/**/*.test.(ts|tsx)'],
|
|
23
25
|
passWithNoTests: true,
|
|
24
26
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/ai-assistant",
|
|
3
|
-
"version": "0.5.1-develop.
|
|
3
|
+
"version": "0.5.1-develop.2694.732417c5ec",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22.0.0"
|
|
@@ -98,12 +98,12 @@
|
|
|
98
98
|
"zod-to-json-schema": "^3.25.2"
|
|
99
99
|
},
|
|
100
100
|
"peerDependencies": {
|
|
101
|
-
"@open-mercato/shared": "0.5.1-develop.
|
|
102
|
-
"@open-mercato/ui": "0.5.1-develop.
|
|
101
|
+
"@open-mercato/shared": "0.5.1-develop.2694.732417c5ec",
|
|
102
|
+
"@open-mercato/ui": "0.5.1-develop.2694.732417c5ec",
|
|
103
103
|
"zod": ">=3.23.0"
|
|
104
104
|
},
|
|
105
105
|
"devDependencies": {
|
|
106
|
-
"@open-mercato/cli": "0.5.1-develop.
|
|
106
|
+
"@open-mercato/cli": "0.5.1-develop.2694.732417c5ec",
|
|
107
107
|
"tsx": "^4.21.0"
|
|
108
108
|
},
|
|
109
109
|
"publishConfig": {
|