@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.
@@ -1,2 +1,2 @@
1
- Found 90 entry points
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 * as esbuild from 'esbuild'
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 __dirname = dirname(fileURLToPath(import.meta.url))
5
+ const packageDir = dirname(fileURLToPath(import.meta.url))
8
6
 
9
- const entryPoints = await glob('src/**/*.{ts,tsx}', {
10
- cwd: __dirname,
11
- ignore: ['**/__tests__/**', '**/*.test.ts', '**/*.test.tsx'],
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')
@@ -24,7 +24,7 @@ async function authenticateMcpRequest(apiKeySecret, container) {
24
24
  });
25
25
  try {
26
26
  apiKey.lastUsedAt = /* @__PURE__ */ new Date();
27
- await em.persistAndFlush(apiKey);
27
+ await em.persist(apiKey).flush();
28
28
  } catch {
29
29
  }
30
30
  return {
@@ -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.persistAndFlush(apiKey)\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": "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,gBAAgB,MAAM;AAAA,IACjC,QAAQ;AAAA,IAER;AAEA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO,OAAO;AAAA,MACd,SAAS,OAAO;AAAA,MAChB,UAAU,OAAO,YAAY;AAAA,MAC7B,gBAAgB,OAAO,kBAAkB;AAAA,MACzC;AAAA,MACA,UAAU,IAAI;AAAA,MACd,cAAc,IAAI;AAAA,IACpB;AAAA,EACF,SAAS,OAAO;AACd,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,YAAQ,MAAM,qCAAqC,OAAO;AAC1D,WAAO,EAAE,SAAS,OAAO,OAAO,wBAAwB;AAAA,EAC1D;AACF;AAiBO,SAAS,oBACd,kBACA,cACA,cACA,aACS;AACT,MAAI,aAAc,QAAO;AACzB,MAAI,CAAC,kBAAkB,OAAQ,QAAO;AAGtC,MAAI,aAAa;AACf,WAAO,YAAY,eAAe,kBAAkB,YAAY;AAAA,EAClE;AAGA,SAAO,iBAAiB,MAAM,CAAC,aAAa;AAC1C,QAAI,aAAa,SAAS,QAAQ,EAAG,QAAO;AAC5C,QAAI,aAAa,SAAS,GAAG,EAAG,QAAO;AAGvC,WAAO,aAAa,KAAK,CAAC,YAAY;AACpC,UAAI,QAAQ,SAAS,IAAI,GAAG;AAC1B,cAAM,SAAS,QAAQ,MAAM,GAAG,EAAE;AAClC,eAAO,SAAS,WAAW,SAAS,GAAG;AAAA,MACzC;AACA,aAAO;AAAA,IACT,CAAC;AAAA,EACH,CAAC;AACH;AAYO,SAAS,yBACd,SACe;AACf,QAAM,YAAY,CAAC,SAAgC;AACjD,QAAI,mBAAmB,SAAS;AAC9B,aAAO,QAAQ,IAAI,IAAI;AAAA,IACzB;AACA,QAAI,mBAAmB,KAAK;AAC1B,aAAO,QAAQ,IAAI,IAAI,KAAK;AAAA,IAC9B;AACA,UAAM,QAAQ,QAAQ,IAAI,KAAK,QAAQ,KAAK,YAAY,CAAC;AACzD,WAAO,OAAO,UAAU,WAAW,QAAQ;AAAA,EAC7C;AAEA,QAAM,UAAU,UAAU,WAAW,GAAG,KAAK;AAC7C,MAAI,SAAS;AACX,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,UAAU,eAAe,GAAG,KAAK;AACpD,MAAI,cAAc,WAAW,YAAY,EAAE,WAAW,SAAS,GAAG;AAChE,WAAO,WAAW,MAAM,CAAC,EAAE,KAAK;AAAA,EAClC;AAEA,SAAO;AACT;",
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
- '^.+\\.tsx?$': [
14
- 'ts-jest',
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.2683.4878a05b8e",
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.2683.4878a05b8e",
102
- "@open-mercato/ui": "0.5.1-develop.2683.4878a05b8e",
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.2683.4878a05b8e",
106
+ "@open-mercato/cli": "0.5.1-develop.2694.732417c5ec",
107
107
  "tsx": "^4.21.0"
108
108
  },
109
109
  "publishConfig": {
@@ -89,7 +89,7 @@ export async function authenticateMcpRequest(
89
89
 
90
90
  try {
91
91
  apiKey.lastUsedAt = new Date()
92
- await em.persistAndFlush(apiKey)
92
+ await em.persist(apiKey).flush()
93
93
  } catch {
94
94
  // Best-effort update; ignore write failures
95
95
  }