@optimizely-opal/opal-tools-sdk 0.1.5-dev → 0.1.8-dev

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.
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ /**
3
+ * Generated by json-schema-to-typescript
4
+ * DO NOT MODIFY - This file is auto-generated from proteus-document-spec.json
5
+ * Run 'npm run generate:proteus' to regenerate
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.UI = void 0;
9
+ /**
10
+ * Builder namespace for Adaptive UI Document components.
11
+ *
12
+ * Usage:
13
+ * UI.Document({ children: [...] })
14
+ * UI.Heading({ children: "Title", level: "2" })
15
+ * UI.Input({ name: "field_name", placeholder: "Enter..." })
16
+ */
17
+ exports.UI = {
18
+ Action: (props) => ({
19
+ $type: "Action",
20
+ ...props,
21
+ }),
22
+ CancelAction: (props) => ({ $type: "CancelAction", ...props }),
23
+ Document: (props) => ({
24
+ $type: "Document",
25
+ ...props,
26
+ }),
27
+ Field: (props) => ({
28
+ $type: "Field",
29
+ ...props,
30
+ }),
31
+ Group: (props) => ({
32
+ $type: "Group",
33
+ ...props,
34
+ }),
35
+ Heading: (props) => ({
36
+ $type: "Heading",
37
+ ...props,
38
+ }),
39
+ Image: (props) => ({
40
+ $type: "Image",
41
+ ...props,
42
+ }),
43
+ Input: (props) => ({
44
+ $type: "Input",
45
+ ...props,
46
+ }),
47
+ Link: (props) => ({
48
+ $type: "Link",
49
+ ...props,
50
+ }),
51
+ Map: (props) => ({
52
+ $type: "Map",
53
+ ...props,
54
+ }),
55
+ MIME_TYPE: "application/vnd.opal.proteus+json",
56
+ Range: (props) => ({
57
+ $type: "Range",
58
+ ...props,
59
+ }),
60
+ Select: (props) => ({
61
+ $type: "Select",
62
+ ...props,
63
+ }),
64
+ SelectContent: (props) => ({ $type: "SelectContent", ...props }),
65
+ SelectTrigger: (props) => ({ $type: "SelectTrigger", ...props }),
66
+ Separator: (props) => ({
67
+ $type: "Separator",
68
+ ...props,
69
+ }),
70
+ Show: (props) => ({
71
+ $type: "Show",
72
+ ...props,
73
+ }),
74
+ Text: (props) => ({
75
+ $type: "Text",
76
+ ...props,
77
+ }),
78
+ Textarea: (props) => ({
79
+ $type: "Textarea",
80
+ ...props,
81
+ }),
82
+ Value: (props) => ({
83
+ $type: "Value",
84
+ ...props,
85
+ }),
86
+ };
@@ -0,0 +1,60 @@
1
+ import { ProteusDocument } from "./proteus";
2
+ type ResourceOptions = {
3
+ /** Description of the resource */
4
+ description?: string;
5
+ /** MIME type of the resource content (e.g., "application/vnd.opal.proteus+json") */
6
+ mimeType?: string;
7
+ /** Human-readable title for the resource */
8
+ title?: string;
9
+ /** The unique URI for this resource (e.g., "ui://my-app/create-form") */
10
+ uri: string;
11
+ };
12
+ /**
13
+ * Register a function as an MCP resource
14
+ *
15
+ * The handler can return either a string or a UI.Document. When returning a UI.Document,
16
+ * the SDK will automatically serialize it to JSON and set the MIME type to
17
+ * "application/vnd.opal.proteus+json" (if not already specified).
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * // Example 1: Returning a string (manual serialization)
22
+ * const getFormResource = registerResource('create-form', {
23
+ * uri: 'ui://my-app/create-form',
24
+ * description: 'Create form UI specification',
25
+ * mimeType: 'application/vnd.opal.proteus+json',
26
+ * title: 'Create Form'
27
+ * }, async () => {
28
+ * return JSON.stringify(proteusSpec);
29
+ * });
30
+ *
31
+ * // Example 2: Returning a UI.Document (automatic serialization)
32
+ * import { UI } from '@optimizely-opal/opal-tools-sdk';
33
+ *
34
+ * const getDynamicForm = registerResource('create-form', {
35
+ * uri: 'ui://my-app/create-form',
36
+ * description: 'Create form UI specification',
37
+ * title: 'Create Form'
38
+ * }, async () => {
39
+ * return UI.Document({
40
+ * appName: 'Item Manager',
41
+ * title: 'Create New Item',
42
+ * body: [
43
+ * UI.Heading({ children: 'Create Item' }),
44
+ * UI.Field({
45
+ * label: 'Name',
46
+ * children: UI.Input({ name: 'item_name' })
47
+ * })
48
+ * ],
49
+ * actions: [UI.Action({ children: 'Save' })]
50
+ * });
51
+ * });
52
+ * ```
53
+ *
54
+ * @param name - Name of the resource
55
+ * @param options - Resource options (uri, description, mimeType, title)
56
+ * @param handler - Async function that returns the resource content (string or UI.Document)
57
+ * @returns The handler function (for convenience)
58
+ */
59
+ export declare function registerResource(name: string, options: ResourceOptions, handler: () => Promise<ProteusDocument | string> | ProteusDocument | string): typeof handler;
60
+ export {};
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerResource = registerResource;
4
+ const registry_1 = require("./registry");
5
+ /**
6
+ * Register a function as an MCP resource
7
+ *
8
+ * The handler can return either a string or a UI.Document. When returning a UI.Document,
9
+ * the SDK will automatically serialize it to JSON and set the MIME type to
10
+ * "application/vnd.opal.proteus+json" (if not already specified).
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * // Example 1: Returning a string (manual serialization)
15
+ * const getFormResource = registerResource('create-form', {
16
+ * uri: 'ui://my-app/create-form',
17
+ * description: 'Create form UI specification',
18
+ * mimeType: 'application/vnd.opal.proteus+json',
19
+ * title: 'Create Form'
20
+ * }, async () => {
21
+ * return JSON.stringify(proteusSpec);
22
+ * });
23
+ *
24
+ * // Example 2: Returning a UI.Document (automatic serialization)
25
+ * import { UI } from '@optimizely-opal/opal-tools-sdk';
26
+ *
27
+ * const getDynamicForm = registerResource('create-form', {
28
+ * uri: 'ui://my-app/create-form',
29
+ * description: 'Create form UI specification',
30
+ * title: 'Create Form'
31
+ * }, async () => {
32
+ * return UI.Document({
33
+ * appName: 'Item Manager',
34
+ * title: 'Create New Item',
35
+ * body: [
36
+ * UI.Heading({ children: 'Create Item' }),
37
+ * UI.Field({
38
+ * label: 'Name',
39
+ * children: UI.Input({ name: 'item_name' })
40
+ * })
41
+ * ],
42
+ * actions: [UI.Action({ children: 'Save' })]
43
+ * });
44
+ * });
45
+ * ```
46
+ *
47
+ * @param name - Name of the resource
48
+ * @param options - Resource options (uri, description, mimeType, title)
49
+ * @param handler - Async function that returns the resource content (string or UI.Document)
50
+ * @returns The handler function (for convenience)
51
+ */
52
+ function registerResource(name, options, handler) {
53
+ console.log(`Registering resource ${name} with URI ${options.uri}`);
54
+ // Register the resource with all services
55
+ for (const service of registry_1.registry.services) {
56
+ service.registerResource(options.uri, name, options.description, options.mimeType, options.title, handler);
57
+ }
58
+ return handler;
59
+ }
@@ -0,0 +1,79 @@
1
+ import { z } from "zod/v4";
2
+ import { Credentials } from "./models";
3
+ /**
4
+ * Extra context passed to tool handlers
5
+ */
6
+ export type RequestHandlerExtra = {
7
+ /** Authentication data if provided */
8
+ auth?: {
9
+ credentials: Credentials;
10
+ provider: string;
11
+ };
12
+ /** Execution mode: 'headless' for non-interactive, 'interactive' for user interaction (defaults to 'headless') */
13
+ mode: "headless" | "interactive";
14
+ };
15
+ type ToolOptions<TSchema extends Record<string, z.ZodTypeAny>> = {
16
+ authRequirements?: {
17
+ provider: string;
18
+ required?: boolean;
19
+ scopeBundle: string;
20
+ };
21
+ description: string;
22
+ inputSchema: TSchema;
23
+ uiResource?: string;
24
+ };
25
+ /**
26
+ * Register a function as an Opal tool
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * const greetTool = registerTool('greet', {
31
+ * description: 'Greet a user',
32
+ * inputSchema: {
33
+ * name: z.string().describe('The name to greet')
34
+ * }
35
+ * }, async (params) => {
36
+ * // params is automatically typed as { name: string }
37
+ * return `Hello, ${params.name}!`;
38
+ * });
39
+ * ```
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * // With auth and execution mode
44
+ * const fetchTool = registerTool('fetch_data', {
45
+ * description: 'Fetch data from API',
46
+ * inputSchema: {
47
+ * url: z.string().describe('URL to fetch')
48
+ * },
49
+ * authRequirements: {
50
+ * provider: 'api-service',
51
+ * scopeBundle: 'read'
52
+ * }
53
+ * }, async (params, extra) => {
54
+ * // extra.mode: 'headless' | 'interactive'
55
+ * // extra.auth: { provider, credentials }
56
+ * const headers = extra?.auth ? { Authorization: extra.auth.credentials.token } : {};
57
+ * return fetch(params.url, { headers });
58
+ * });
59
+ * ```
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * // With UI resource for dynamic rendering
64
+ * const createTaskTool = registerTool('create_task', {
65
+ * description: 'Create a new task',
66
+ * inputSchema: {
67
+ * title: z.string().describe('Task title'),
68
+ * description: z.string().describe('Task description')
69
+ * },
70
+ * uiResource: 'ui://my-app/create-form'
71
+ * }, async (params) => {
72
+ * return { id: 'task-123', ...params };
73
+ * });
74
+ * ```
75
+ */
76
+ export declare function registerTool<TSchema extends Record<string, z.ZodTypeAny>>(name: string, options: ToolOptions<TSchema>, handler: (params: {
77
+ [K in keyof TSchema]: z.infer<TSchema[K]>;
78
+ }, extra?: RequestHandlerExtra) => Promise<unknown> | unknown): typeof handler;
79
+ export {};
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerTool = registerTool;
4
+ const v4_1 = require("zod/v4");
5
+ const models_1 = require("./models");
6
+ const registry_1 = require("./registry");
7
+ /**
8
+ * Register a function as an Opal tool
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * const greetTool = registerTool('greet', {
13
+ * description: 'Greet a user',
14
+ * inputSchema: {
15
+ * name: z.string().describe('The name to greet')
16
+ * }
17
+ * }, async (params) => {
18
+ * // params is automatically typed as { name: string }
19
+ * return `Hello, ${params.name}!`;
20
+ * });
21
+ * ```
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * // With auth and execution mode
26
+ * const fetchTool = registerTool('fetch_data', {
27
+ * description: 'Fetch data from API',
28
+ * inputSchema: {
29
+ * url: z.string().describe('URL to fetch')
30
+ * },
31
+ * authRequirements: {
32
+ * provider: 'api-service',
33
+ * scopeBundle: 'read'
34
+ * }
35
+ * }, async (params, extra) => {
36
+ * // extra.mode: 'headless' | 'interactive'
37
+ * // extra.auth: { provider, credentials }
38
+ * const headers = extra?.auth ? { Authorization: extra.auth.credentials.token } : {};
39
+ * return fetch(params.url, { headers });
40
+ * });
41
+ * ```
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * // With UI resource for dynamic rendering
46
+ * const createTaskTool = registerTool('create_task', {
47
+ * description: 'Create a new task',
48
+ * inputSchema: {
49
+ * title: z.string().describe('Task title'),
50
+ * description: z.string().describe('Task description')
51
+ * },
52
+ * uiResource: 'ui://my-app/create-form'
53
+ * }, async (params) => {
54
+ * return { id: 'task-123', ...params };
55
+ * });
56
+ * ```
57
+ */
58
+ function registerTool(name, options, handler) {
59
+ // Register the tool with all services
60
+ for (const service of registry_1.registry.services) {
61
+ service.registerTool(name, options.description, handler, jsonSchemaToParameters(v4_1.z.toJSONSchema(v4_1.z.object(options.inputSchema))), `/tools/${name.replace(/_/g, "-")}`, options.authRequirements
62
+ ? [
63
+ new models_1.AuthRequirement(options.authRequirements.provider, options.authRequirements.scopeBundle, options.authRequirements.required ?? true),
64
+ ]
65
+ : undefined, true, options.uiResource);
66
+ }
67
+ return handler;
68
+ }
69
+ /**
70
+ * Convert JSON Schema to Parameter definitions
71
+ * This converts the output of z.toJSONSchema() back to the Parameter[] format
72
+ * expected by the legacy discovery endpoint
73
+ */
74
+ function jsonSchemaToParameters(jsonSchema) {
75
+ const parameters = [];
76
+ if (!jsonSchema.properties) {
77
+ return parameters;
78
+ }
79
+ const required = jsonSchema.required || [];
80
+ for (const [key, value] of Object.entries(jsonSchema.properties)) {
81
+ const prop = value;
82
+ parameters.push(new models_1.Parameter(key, jsonSchemaTypeToParameterType(prop.type || "string"), prop.description || "", required.includes(key)));
83
+ }
84
+ return parameters;
85
+ }
86
+ /**
87
+ * Map JSON Schema type to ParameterType
88
+ */
89
+ function jsonSchemaTypeToParameterType(jsonType) {
90
+ switch (jsonType) {
91
+ case "array":
92
+ return models_1.ParameterType.List;
93
+ case "boolean":
94
+ return models_1.ParameterType.Boolean;
95
+ case "integer":
96
+ return models_1.ParameterType.Integer;
97
+ case "number":
98
+ return models_1.ParameterType.Number;
99
+ case "object":
100
+ return models_1.ParameterType.Dictionary;
101
+ case "string":
102
+ return models_1.ParameterType.String;
103
+ default:
104
+ return models_1.ParameterType.String;
105
+ }
106
+ }
@@ -1,4 +1,4 @@
1
- import { ToolsService } from './service';
1
+ import { ToolsService } from "./service";
2
2
  /**
3
3
  * Internal registry for ToolsService instances
4
4
  */
package/dist/registry.js CHANGED
@@ -5,5 +5,5 @@ exports.registry = void 0;
5
5
  * Internal registry for ToolsService instances
6
6
  */
7
7
  exports.registry = {
8
- services: []
8
+ services: [],
9
9
  };
package/dist/service.d.ts CHANGED
@@ -1,18 +1,26 @@
1
- import { Express } from 'express';
2
- import { AuthRequirement, Parameter } from './models';
1
+ import { Express } from "express";
2
+ import { AuthRequirement, Parameter } from "./models";
3
+ import { ProteusDocument } from "./proteus";
3
4
  export declare class ToolsService {
4
5
  private app;
5
- private router;
6
6
  private functions;
7
+ private resources;
8
+ private router;
7
9
  /**
8
10
  * Initialize a new tools service
9
11
  * @param app Express application
10
12
  */
11
13
  constructor(app: Express);
12
14
  /**
13
- * Initialize the discovery endpoint
15
+ * Register a resource function
16
+ * @param uri The unique URI for this resource (e.g., "ui://my-app/create-form")
17
+ * @param name Name of the resource
18
+ * @param description Description of the resource (optional)
19
+ * @param mimeType MIME type of the resource content (optional)
20
+ * @param title Human-readable title for the resource (optional)
21
+ * @param handler Function implementing the resource (returns string or ProteusDocument)
14
22
  */
15
- private initRoutes;
23
+ registerResource(uri: string, name: string, description: string | undefined, mimeType: string | undefined, title: string | undefined, handler: () => Promise<ProteusDocument | string> | ProteusDocument | string): void;
16
24
  /**
17
25
  * Register a tool function
18
26
  * @param name Tool name
@@ -21,7 +29,13 @@ export declare class ToolsService {
21
29
  * @param parameters List of parameters for the tool
22
30
  * @param endpoint API endpoint for the tool
23
31
  * @param authRequirements Authentication requirements (optional)
32
+ * @param isNewStyle Whether this is a new-style tool (registerTool) vs legacy decorator
33
+ * @param uiResource URI of associated UI resource for dynamic rendering (optional)
24
34
  */
25
35
  registerTool(name: string, description: string, handler: any, // Changed from Function to any to avoid confusion with built-in Function type
26
- parameters: Parameter[], endpoint: string, authRequirements?: AuthRequirement[]): void;
36
+ parameters: Parameter[], endpoint: string, authRequirements?: AuthRequirement[], isNewStyle?: boolean, uiResource?: string): void;
37
+ /**
38
+ * Initialize the discovery endpoint and resources/read endpoint
39
+ */
40
+ private initRoutes;
27
41
  }
package/dist/service.js CHANGED
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.ToolsService = void 0;
7
7
  const express_1 = __importDefault(require("express"));
8
8
  const models_1 = require("./models");
9
+ const proteus_1 = require("./proteus");
9
10
  const registry_1 = require("./registry");
10
11
  class ToolsService {
11
12
  /**
@@ -14,6 +15,7 @@ class ToolsService {
14
15
  */
15
16
  constructor(app) {
16
17
  this.functions = [];
18
+ this.resources = new Map();
17
19
  this.app = app;
18
20
  this.router = express_1.default.Router();
19
21
  this.initRoutes();
@@ -21,13 +23,19 @@ class ToolsService {
21
23
  registry_1.registry.services.push(this);
22
24
  }
23
25
  /**
24
- * Initialize the discovery endpoint
26
+ * Register a resource function
27
+ * @param uri The unique URI for this resource (e.g., "ui://my-app/create-form")
28
+ * @param name Name of the resource
29
+ * @param description Description of the resource (optional)
30
+ * @param mimeType MIME type of the resource content (optional)
31
+ * @param title Human-readable title for the resource (optional)
32
+ * @param handler Function implementing the resource (returns string or ProteusDocument)
25
33
  */
26
- initRoutes() {
27
- this.router.get('/discovery', (req, res) => {
28
- res.json({ functions: this.functions.map(f => f.toJSON()) });
29
- });
30
- this.app.use(this.router);
34
+ registerResource(uri, name, description, mimeType, title, handler) {
35
+ console.log(`Registering resource: ${name} with URI: ${uri}`);
36
+ const metadata = new models_1.Resource(uri, name, description, mimeType, title);
37
+ // Store both metadata and handler together
38
+ this.resources.set(uri, { handler, metadata });
31
39
  }
32
40
  /**
33
41
  * Register a tool function
@@ -37,10 +45,14 @@ class ToolsService {
37
45
  * @param parameters List of parameters for the tool
38
46
  * @param endpoint API endpoint for the tool
39
47
  * @param authRequirements Authentication requirements (optional)
48
+ * @param isNewStyle Whether this is a new-style tool (registerTool) vs legacy decorator
49
+ * @param uiResource URI of associated UI resource for dynamic rendering (optional)
40
50
  */
41
- registerTool(name, description, handler, // Changed from Function to any to avoid confusion with built-in Function type
42
- parameters, endpoint, authRequirements) {
43
- const func = new models_1.Function(name, description, parameters, endpoint, authRequirements);
51
+ registerTool(name, description,
52
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
53
+ handler, // Changed from Function to any to avoid confusion with built-in Function type
54
+ parameters, endpoint, authRequirements, isNewStyle = false, uiResource) {
55
+ const func = new models_1.Function(name, description, parameters, endpoint, authRequirements, uiResource);
44
56
  this.functions.push(func);
45
57
  // Register the actual endpoint
46
58
  this.router.post(endpoint, async (req, res) => {
@@ -61,28 +73,103 @@ class ToolsService {
61
73
  // Extract auth data if available
62
74
  const authData = req.body && req.body.auth;
63
75
  if (authData) {
64
- console.log(`Auth data provided for provider: ${authData.provider || 'unknown'}`);
76
+ console.log(`Auth data provided for provider: ${authData.provider || "unknown"}`);
65
77
  }
66
- // Call the handler with extracted parameters and auth data
67
- // Check if handler accepts auth as third parameter
68
- const handlerParamCount = handler.length;
78
+ // Call the handler with extracted parameters
69
79
  let result;
70
- if (handlerParamCount >= 2) {
71
- // Handler accepts auth data
72
- result = await handler(params, authData);
80
+ if (isNewStyle) {
81
+ result = await handler(params, {
82
+ mode: (req.body && req.body.execution_mode) || "headless",
83
+ ...(authData && { auth: authData }),
84
+ });
73
85
  }
74
86
  else {
75
- // Handler doesn't accept auth data
76
- result = await handler(params);
87
+ // Check if handler accepts auth as third parameter
88
+ const handlerParamCount = handler.length;
89
+ if (handlerParamCount >= 2) {
90
+ // Handler accepts auth data
91
+ result = await handler(params, authData);
92
+ }
93
+ else {
94
+ // Handler doesn't accept auth data
95
+ result = await handler(params);
96
+ }
77
97
  }
78
98
  console.log(`Tool ${name} returned:`, result);
79
99
  res.json(result);
80
100
  }
81
101
  catch (error) {
82
102
  console.error(`Error in tool ${name}:`, error);
83
- res.status(500).json({ error: error.message || 'Unknown error' });
103
+ res.status(500).json({
104
+ error: error instanceof Error ? error.message : "Unknown error",
105
+ });
106
+ }
107
+ });
108
+ }
109
+ /**
110
+ * Initialize the discovery endpoint and resources/read endpoint
111
+ */
112
+ initRoutes() {
113
+ this.router.get("/discovery", (_req, res) => {
114
+ res.json({
115
+ functions: this.functions.map((f) => f.toJSON()),
116
+ });
117
+ });
118
+ // POST /resources/read endpoint for MCP protocol
119
+ this.router.post("/resources/read", async (req, res) => {
120
+ try {
121
+ const { uri } = req.body;
122
+ if (!uri) {
123
+ return res.status(400).json({
124
+ error: "Missing required field: uri",
125
+ });
126
+ }
127
+ console.log(`Received resource read request for URI: ${uri}`);
128
+ // Find the resource registration
129
+ const registration = this.resources.get(uri);
130
+ if (!registration) {
131
+ return res.status(404).json({
132
+ error: `Resource not found: ${uri}`,
133
+ });
134
+ }
135
+ // Call handler and await result
136
+ const content = await registration.handler();
137
+ let textContent;
138
+ let mimeType = registration.metadata.mimeType;
139
+ // Check if handler returned a ProteusDocument
140
+ if (typeof content === "object" &&
141
+ content !== null &&
142
+ "$type" in content &&
143
+ content.$type === "Document") {
144
+ // Auto-serialize to JSON
145
+ textContent = JSON.stringify(content);
146
+ // Auto-set MIME type if not specified
147
+ if (!mimeType) {
148
+ mimeType = proteus_1.UI.MIME_TYPE;
149
+ }
150
+ }
151
+ else if (typeof content === "string") {
152
+ textContent = content;
153
+ }
154
+ else {
155
+ throw new Error(`Resource handler for '${uri}' must return a string or ProteusDocument, but returned ${typeof content}`);
156
+ }
157
+ console.log(`Resource ${uri} returned content of length: ${textContent.length}`);
158
+ // Return the resource content directly
159
+ res.json({
160
+ mimeType: mimeType || "text/plain",
161
+ text: textContent,
162
+ uri,
163
+ });
164
+ }
165
+ catch (error) {
166
+ console.error(`Error reading resource:`, error);
167
+ res.status(500).json({
168
+ error: error instanceof Error ? error.message : "Unknown error",
169
+ });
84
170
  }
85
171
  });
172
+ this.app.use(this.router);
86
173
  }
87
174
  }
88
175
  exports.ToolsService = ToolsService;
@@ -0,0 +1,20 @@
1
+ const typescript = require("typescript-eslint");
2
+ const perfectionist = require("eslint-plugin-perfectionist");
3
+
4
+ module.exports = typescript.config(
5
+ {
6
+ ignores: ["dist/**", "node_modules/**", "*.config.js"],
7
+ },
8
+ ...typescript.configs.recommended,
9
+ perfectionist.configs["recommended-natural"],
10
+ {
11
+ files: ["**/*.ts"],
12
+ ignores: ["*.config.ts"],
13
+ languageOptions: {
14
+ parserOptions: {
15
+ projectService: true,
16
+ tsconfigRootDir: __dirname,
17
+ },
18
+ },
19
+ },
20
+ );