tool-server-template 0.80.0

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 (89) hide show
  1. package/.env.example +14 -0
  2. package/LICENSE +13 -0
  3. package/README.md +611 -0
  4. package/TESTING.md +480 -0
  5. package/api/index.js +31 -0
  6. package/lib/build-site.d.ts +2 -0
  7. package/lib/build-site.d.ts.map +1 -0
  8. package/lib/build-site.js +42 -0
  9. package/lib/build-site.js.map +1 -0
  10. package/lib/copy-assets.d.ts +8 -0
  11. package/lib/copy-assets.d.ts.map +1 -0
  12. package/lib/copy-assets.js +62 -0
  13. package/lib/copy-assets.js.map +1 -0
  14. package/lib/interactions/index.d.ts +2 -0
  15. package/lib/interactions/index.d.ts.map +1 -0
  16. package/lib/interactions/index.js +10 -0
  17. package/lib/interactions/index.js.map +1 -0
  18. package/lib/interactions/summarize/icon.svg.d.ts +3 -0
  19. package/lib/interactions/summarize/icon.svg.d.ts.map +1 -0
  20. package/lib/interactions/summarize/icon.svg.js +10 -0
  21. package/lib/interactions/summarize/icon.svg.js.map +1 -0
  22. package/lib/interactions/summarize/index.d.ts +3 -0
  23. package/lib/interactions/summarize/index.d.ts.map +1 -0
  24. package/lib/interactions/summarize/index.js +14 -0
  25. package/lib/interactions/summarize/index.js.map +1 -0
  26. package/lib/interactions/summarize/text_summarizer/index.d.ts +53 -0
  27. package/lib/interactions/summarize/text_summarizer/index.d.ts.map +1 -0
  28. package/lib/interactions/summarize/text_summarizer/index.js +56 -0
  29. package/lib/interactions/summarize/text_summarizer/index.js.map +1 -0
  30. package/lib/interactions/summarize/text_summarizer/prompt.jst +31 -0
  31. package/lib/interactions/summarize/text_summarizer/prompt.jst.js +4 -0
  32. package/lib/interactions/summarize/text_summarizer/prompt.jst.js.map +1 -0
  33. package/lib/server-node.d.ts +2 -0
  34. package/lib/server-node.d.ts.map +1 -0
  35. package/lib/server-node.js +14 -0
  36. package/lib/server-node.js.map +1 -0
  37. package/lib/server.d.ts +3 -0
  38. package/lib/server.d.ts.map +1 -0
  39. package/lib/server.js +17 -0
  40. package/lib/server.js.map +1 -0
  41. package/lib/skills/code-review/SKILL.md +59 -0
  42. package/lib/skills/index.d.ts +4 -0
  43. package/lib/skills/index.d.ts.map +1 -0
  44. package/lib/skills/index.js +14 -0
  45. package/lib/skills/index.js.map +1 -0
  46. package/lib/tools/calculator/calculator.d.ts +23 -0
  47. package/lib/tools/calculator/calculator.d.ts.map +1 -0
  48. package/lib/tools/calculator/calculator.js +42 -0
  49. package/lib/tools/calculator/calculator.js.map +1 -0
  50. package/lib/tools/calculator/icon.svg.d.ts +3 -0
  51. package/lib/tools/calculator/icon.svg.d.ts.map +1 -0
  52. package/lib/tools/calculator/icon.svg.js +10 -0
  53. package/lib/tools/calculator/icon.svg.js.map +1 -0
  54. package/lib/tools/calculator/index.d.ts +3 -0
  55. package/lib/tools/calculator/index.d.ts.map +1 -0
  56. package/lib/tools/calculator/index.js +14 -0
  57. package/lib/tools/calculator/index.js.map +1 -0
  58. package/lib/tools/calculator/manifest.d.ts +16 -0
  59. package/lib/tools/calculator/manifest.d.ts.map +1 -0
  60. package/lib/tools/calculator/manifest.js +17 -0
  61. package/lib/tools/calculator/manifest.js.map +1 -0
  62. package/lib/tools/index.d.ts +2 -0
  63. package/lib/tools/index.d.ts.map +1 -0
  64. package/lib/tools/index.js +8 -0
  65. package/lib/tools/index.js.map +1 -0
  66. package/package.json +51 -0
  67. package/public/.gitkeep +1 -0
  68. package/rollup.config.bundle.js +60 -0
  69. package/rollup.config.js +99 -0
  70. package/src/build-site.ts +77 -0
  71. package/src/copy-assets.ts +104 -0
  72. package/src/interactions/index.ts +7 -0
  73. package/src/interactions/summarize/icon.svg.ts +7 -0
  74. package/src/interactions/summarize/index.ts +11 -0
  75. package/src/interactions/summarize/text_summarizer/index.ts +53 -0
  76. package/src/interactions/summarize/text_summarizer/prompt.jst +31 -0
  77. package/src/raw-imports.d.ts +4 -0
  78. package/src/server-node.ts +24 -0
  79. package/src/server.ts +22 -0
  80. package/src/skills/code-review/SKILL.md +59 -0
  81. package/src/skills/index.ts +12 -0
  82. package/src/tools/calculator/calculator.ts +60 -0
  83. package/src/tools/calculator/icon.svg.ts +7 -0
  84. package/src/tools/calculator/index.ts +11 -0
  85. package/src/tools/calculator/manifest.ts +16 -0
  86. package/src/tools/index.ts +5 -0
  87. package/template.config.json +27 -0
  88. package/tsconfig.json +25 -0
  89. package/vercel.json +29 -0
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Rollup Configuration for building tools as es bundles for import() usage
3
+ *
4
+ * Not used for now.
5
+ *
6
+ * Creates browser-ready bundles for each tool collection in lib/tools
7
+ * Input: lib/tools/{TOOL_DIR}/index.js (already compiled from TypeScript)
8
+ * Output: dist/libs/tool-server-{name}.js (browser bundles)
9
+ */
10
+ import { nodeResolve } from '@rollup/plugin-node-resolve';
11
+ import commonjs from '@rollup/plugin-commonjs';
12
+ import json from '@rollup/plugin-json';
13
+ import { terser } from 'rollup-plugin-terser';
14
+ import fs from 'fs';
15
+ import path from 'path';
16
+
17
+ const libToolCollectionsDir = './lib/tools';
18
+ const outputDir = './dist/libs';
19
+
20
+ // Ensure output directory exists
21
+ if (!fs.existsSync(outputDir)) {
22
+ fs.mkdirSync(outputDir, { recursive: true });
23
+ }
24
+
25
+ // Get all directories in lib/tools with an index.js
26
+ const entries = fs.existsSync(libToolCollectionsDir)
27
+ ? fs.readdirSync(libToolCollectionsDir).filter((name) => {
28
+ const dir = path.join(libToolCollectionsDir, name);
29
+ return (
30
+ fs.statSync(dir).isDirectory() &&
31
+ fs.existsSync(path.join(dir, 'index.js'))
32
+ );
33
+ })
34
+ : [];
35
+
36
+ // Create a bundle configuration for each tool collection
37
+ const browserBundles = entries.map((name) => ({
38
+ input: path.join(libToolCollectionsDir, name, 'index.js'),
39
+ output: {
40
+ file: path.join(outputDir, `tool-server-${name}.js`),
41
+ format: 'es',
42
+ sourcemap: true,
43
+ inlineDynamicImports: true
44
+ },
45
+ plugins: [
46
+ nodeResolve({
47
+ browser: true,
48
+ preferBuiltins: false
49
+ }),
50
+ json(),
51
+ commonjs(),
52
+ terser({
53
+ compress: {
54
+ drop_console: false
55
+ }
56
+ })
57
+ ]
58
+ }));
59
+
60
+ export default browserBundles;
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Rollup Configuration for Server Build
3
+ *
4
+ * This configuration handles:
5
+ * 1. TypeScript compilation (src → lib) with preserveModules
6
+ * 2. Raw file imports (?raw) for template files
7
+ *
8
+ * Browser bundles are in rollup.config.browser.js
9
+ */
10
+ import json from '@rollup/plugin-json';
11
+ import typescript from '@rollup/plugin-typescript';
12
+ import fs from 'fs';
13
+ import path from 'path';
14
+
15
+ // ============================================================================
16
+ // Raw Plugin - Supports ?raw imports for template files
17
+ // ============================================================================
18
+ function rawPlugin() {
19
+ return {
20
+ name: 'raw-loader',
21
+ resolveId(id, importer) {
22
+ if (id.endsWith('?raw')) {
23
+ const cleanId = id.slice(0, -4); // Remove '?raw'
24
+ if (cleanId.startsWith('.') && importer) {
25
+ // Resolve relative path
26
+ const resolved = path.resolve(path.dirname(importer), cleanId);
27
+ return resolved + '?raw';
28
+ }
29
+ return id;
30
+ }
31
+ },
32
+ load(id) {
33
+ if (id.endsWith('?raw')) {
34
+ const filePath = id.slice(0, -4); // Remove '?raw'
35
+ const content = fs.readFileSync(filePath, 'utf-8');
36
+ return `export default ${JSON.stringify(content)}`;
37
+ }
38
+ }
39
+ };
40
+ }
41
+
42
+ // ============================================================================
43
+ // Exit Plugin - Forces process exit after build completes
44
+ // This prevents TypeScript plugin from keeping the process alive
45
+ // ============================================================================
46
+ function exitPlugin() {
47
+ return {
48
+ name: 'force-exit',
49
+ closeBundle() {
50
+ console.log('Tool server build Done');
51
+ // Force exit after bundle completes to prevent hanging
52
+ setImmediate(() => process.exit(0));
53
+ }
54
+ };
55
+ }
56
+
57
+ // ============================================================================
58
+ // Server Build Configuration (TypeScript → JavaScript)
59
+ // ============================================================================
60
+ const serverBuild = {
61
+ input: {
62
+ server: './src/server.ts',
63
+ 'server-node': './src/server-node.ts',
64
+ 'build-site': './src/build-site.ts',
65
+ 'copy-assets': './src/copy-assets.ts'
66
+ },
67
+ output: {
68
+ dir: 'lib',
69
+ format: 'es',
70
+ sourcemap: true,
71
+ preserveModules: true,
72
+ preserveModulesRoot: 'src',
73
+ entryFileNames: '[name].js'
74
+ },
75
+ external: (id) => {
76
+ // Keep relative imports as part of the bundle
77
+ if (id.startsWith('.') || id.startsWith('/')) {
78
+ return false;
79
+ }
80
+ // Externalize all node modules and absolute imports
81
+ return true;
82
+ },
83
+ plugins: [
84
+ rawPlugin(),
85
+ typescript({
86
+ tsconfig: './tsconfig.json',
87
+ declaration: true,
88
+ declarationDir: 'lib',
89
+ sourceMap: true
90
+ }),
91
+ json(),
92
+ exitPlugin()
93
+ ]
94
+ };
95
+
96
+ // ============================================================================
97
+ // Export server build configuration only
98
+ // ============================================================================
99
+ export default serverBuild;
@@ -0,0 +1,77 @@
1
+ import {
2
+ indexPage,
3
+ interactionCollectionPage,
4
+ skillCollectionPage,
5
+ toolCollectionPage
6
+ } from "@vertesia/tools-sdk";
7
+ import { mkdirSync, writeFileSync } from "node:fs";
8
+ import { loadInteractions } from "./interactions/index.js";
9
+ import { skills } from "./skills/index.js";
10
+ import { tools } from "./tools/index.js";
11
+
12
+ /**
13
+ * Generates static HTML pages for all tool collections, skills, and interactions
14
+ * This runs during the build process to create browsable documentation
15
+ */
16
+ async function build(outDir: string) {
17
+ console.log(`Building static site to ${outDir}...`);
18
+
19
+ // Ensure output directory exists
20
+ mkdirSync(outDir, { recursive: true });
21
+
22
+ // Load interactions
23
+ const interactions = await loadInteractions();
24
+
25
+ // Create main index page
26
+ console.log('Creating index page...');
27
+ writeFileSync(
28
+ `${outDir}/index.html`,
29
+ indexPage(tools, skills, interactions, 'Tool Server Template')
30
+ );
31
+
32
+ // Create pages for each tool collection
33
+ console.log(`Creating ${tools.length} tool collection pages...`);
34
+ for (const coll of tools) {
35
+ const dir = `${outDir}/tools/${coll.name}`;
36
+ mkdirSync(dir, { recursive: true });
37
+ writeFileSync(
38
+ `${dir}/index.html`,
39
+ toolCollectionPage(coll)
40
+ );
41
+ }
42
+
43
+ // Create pages for each skill collection
44
+ console.log(`Creating ${skills.length} skill collection pages...`);
45
+ for (const coll of skills) {
46
+ const dir = `${outDir}/skills/${coll.name}`;
47
+ mkdirSync(dir, { recursive: true });
48
+ writeFileSync(
49
+ `${dir}/index.html`,
50
+ skillCollectionPage(coll)
51
+ );
52
+ }
53
+
54
+ // Create pages for each interaction collection
55
+ console.log(`Creating ${interactions.length} interaction collection pages...`);
56
+ for (const coll of interactions) {
57
+ const dir = `${outDir}/interactions/${coll.name}`;
58
+ mkdirSync(dir, { recursive: true });
59
+ writeFileSync(
60
+ `${dir}/index.html`,
61
+ interactionCollectionPage(coll)
62
+ );
63
+ }
64
+
65
+ console.log('✓ Static site build complete!');
66
+ }
67
+
68
+ // Run the build
69
+ const outDir = process.argv[2] || './dist';
70
+ build(outDir)
71
+ .then(() => {
72
+ process.exit(0);
73
+ })
74
+ .catch(error => {
75
+ console.error('Build failed:', error);
76
+ process.exit(1);
77
+ });
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Copy runtime assets (skill files, prompt files, scripts) to dist folder
4
+ * These files are read from disk at runtime and need to be deployed with the app
5
+ *
6
+ * Usage:
7
+ * npx tools-sdk-copy-assets [srcDir] [distDir]
8
+ *
9
+ * Or import and call directly:
10
+ * import { copyRuntimeAssets } from '@vertesia/tools-sdk';
11
+ * copyRuntimeAssets('./src', './dist');
12
+ */
13
+ import { existsSync, readdirSync, statSync, mkdirSync, copyFileSync } from "fs";
14
+ import { dirname, join } from "path";
15
+
16
+ /**
17
+ * Recursively copy files matching a filter
18
+ */
19
+ function copyFilesRecursive(src: string, dest: string, fileFilter: (filename: string) => boolean): void {
20
+ if (!existsSync(src)) return;
21
+
22
+ const entries = readdirSync(src);
23
+
24
+ for (const entry of entries) {
25
+ const srcPath = join(src, entry);
26
+ const destPath = join(dest, entry);
27
+ const stat = statSync(srcPath);
28
+
29
+ if (stat.isDirectory()) {
30
+ // Recurse into directories
31
+ copyFilesRecursive(srcPath, destPath, fileFilter);
32
+ } else if (fileFilter(entry)) {
33
+ // Copy matching files
34
+ mkdirSync(dirname(destPath), { recursive: true });
35
+ copyFileSync(srcPath, destPath);
36
+ }
37
+ }
38
+ }
39
+
40
+ export interface CopyAssetsOptions {
41
+ /** Source directory (default: './src') */
42
+ srcDir?: string;
43
+ /** Destination directory (default: './dist') */
44
+ distDir?: string;
45
+ /** Whether to log progress (default: true) */
46
+ verbose?: boolean;
47
+ }
48
+
49
+ /**
50
+ * Copy runtime assets (skills, interactions) from src to dist
51
+ */
52
+ export function copyRuntimeAssets(options: CopyAssetsOptions = {}): void {
53
+ const {
54
+ srcDir = './src',
55
+ distDir = './dist',
56
+ verbose = true
57
+ } = options;
58
+
59
+ if (verbose) {
60
+ console.log('Copying runtime assets to dist...');
61
+ }
62
+
63
+ // Copy skill files (SKILL.md, SKILL.jst, *.py)
64
+ const skillsSrc = join(srcDir, 'skills');
65
+ const skillsDest = join(distDir, 'skills');
66
+
67
+ if (existsSync(skillsSrc)) {
68
+ copyFilesRecursive(skillsSrc, skillsDest, (filename) => {
69
+ return filename === 'SKILL.md' ||
70
+ filename === 'SKILL.jst' ||
71
+ filename.endsWith('.py');
72
+ });
73
+ if (verbose) {
74
+ console.log(' ✓ Skills assets (SKILL.md, SKILL.jst, *.py)');
75
+ }
76
+ }
77
+
78
+ // Copy interaction prompt files (prompt.jst, prompt.md)
79
+ const interactionsSrc = join(srcDir, 'interactions');
80
+ const interactionsDest = join(distDir, 'interactions');
81
+
82
+ if (existsSync(interactionsSrc)) {
83
+ copyFilesRecursive(interactionsSrc, interactionsDest, (filename) => {
84
+ return filename === 'prompt.jst' ||
85
+ filename === 'prompt.md';
86
+ });
87
+ if (verbose) {
88
+ console.log(' ✓ Interaction assets (prompt.jst, prompt.md)');
89
+ }
90
+ }
91
+
92
+ if (verbose) {
93
+ console.log('Runtime assets copied successfully!');
94
+ }
95
+ }
96
+
97
+ // CLI entry point
98
+ if (typeof process !== 'undefined' && process.argv[1]?.includes('copy-assets')) {
99
+ const args = process.argv.slice(2);
100
+ const srcDir = args[0] || './src';
101
+ const distDir = args[1] || './dist';
102
+
103
+ copyRuntimeAssets({ srcDir, distDir });
104
+ }
@@ -0,0 +1,7 @@
1
+ import { SummarizeInteractions } from "./summarize/index.js";
2
+
3
+ export async function loadInteractions() {
4
+ return [
5
+ SummarizeInteractions
6
+ ];
7
+ }
@@ -0,0 +1,7 @@
1
+ export default `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
3
+ <polyline points="14 2 14 8 20 8"/>
4
+ <line x1="16" y1="13" x2="8" y2="13"/>
5
+ <line x1="16" y1="17" x2="8" y2="17"/>
6
+ <polyline points="10 9 9 9 8 9"/>
7
+ </svg>`;
@@ -0,0 +1,11 @@
1
+ import { InteractionCollection } from "@vertesia/tools-sdk";
2
+ import textSummarizer from "./text_summarizer/index.js";
3
+ import icon from "./icon.svg.js";
4
+
5
+ export const SummarizeInteractions = new InteractionCollection({
6
+ name: "summarize",
7
+ title: "Summarization Interactions",
8
+ description: "A collection of interactions for summarizing and condensing text",
9
+ icon,
10
+ interactions: [textSummarizer]
11
+ });
@@ -0,0 +1,53 @@
1
+ import { PromptRole } from "@llumiverse/common";
2
+ import { InteractionSpec, TemplateType } from "@vertesia/common";
3
+ import PROMPT_CONTENT from "./prompt.jst?raw";
4
+
5
+ export default {
6
+ name: "text_summarizer",
7
+ title: "Text Summarizer",
8
+ description: "Summarizes text according to specified parameters like style, length, and format.",
9
+ result_schema: {
10
+ type: "object",
11
+ properties: {
12
+ summary: {
13
+ type: "string",
14
+ description: "The generated summary"
15
+ },
16
+ word_count: {
17
+ type: "number",
18
+ description: "Number of words in the summary"
19
+ }
20
+ },
21
+ required: ["summary"]
22
+ },
23
+ prompts: [{
24
+ role: PromptRole.user,
25
+ content: PROMPT_CONTENT,
26
+ content_type: TemplateType.jst,
27
+ schema: {
28
+ type: "object",
29
+ properties: {
30
+ text: {
31
+ type: "string",
32
+ description: "The text to summarize"
33
+ },
34
+ style: {
35
+ type: "string",
36
+ enum: ["concise", "detailed", "bullet-points", "executive"],
37
+ description: "The summary style"
38
+ },
39
+ maxLength: {
40
+ type: "number",
41
+ description: "Maximum length in words (optional)"
42
+ },
43
+ format: {
44
+ type: "string",
45
+ enum: ["paragraph", "bullet-points", "structured"],
46
+ description: "Output format (optional)"
47
+ }
48
+ },
49
+ required: ["text"]
50
+ }
51
+ }],
52
+ tags: ["text", "summarization", "nlp", "content"]
53
+ } satisfies InteractionSpec;
@@ -0,0 +1,31 @@
1
+ return `
2
+ # Text Summarization Request
3
+
4
+ Please summarize the following text according to the specifications below.
5
+
6
+ ## Text to Summarize
7
+ ${text}
8
+
9
+ ## Summarization Parameters
10
+ - **Style**: ${style || 'concise'}
11
+ - **Max Length**: ${maxLength ? maxLength + ' words' : 'flexible'}
12
+ ${format ? `- **Output Format**: ${format}` : ''}
13
+
14
+ ## Instructions
15
+
16
+ 1. Read and understand the full text
17
+ 2. Identify the main points and key information
18
+ 3. Create a summary that:
19
+ - Captures the essential meaning
20
+ - Maintains accuracy
21
+ - Uses clear, simple language
22
+ - Follows the ${style} style
23
+ ${maxLength ? `- Stays within ${maxLength} words` : ''}
24
+ ${format ? `- Is formatted as ${format}` : ''}
25
+
26
+ 4. If the text contains multiple topics, organize your summary accordingly
27
+
28
+ ## Output
29
+
30
+ Provide your summary below:
31
+ `;
@@ -0,0 +1,4 @@
1
+ declare module "*?raw" {
2
+ const content: string;
3
+ export default content;
4
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Node.js HTTP Server Entry Point
3
+ *
4
+ * This file starts a standalone Node.js HTTP server using @hono/node-server.
5
+ * Use this for:
6
+ * - Local development and testing
7
+ * - Deploying to Cloud Run, Railway, Fly.io, etc.
8
+ * - Running in Docker containers
9
+ */
10
+ import { serve } from '@hono/node-server';
11
+ import server from './server.js';
12
+
13
+ const port = parseInt(process.env.PORT || '3000', 10);
14
+
15
+ console.log(`Starting Tool Server on port ${port}...`);
16
+ console.log(`API endpoint: http://localhost:${port}/api`);
17
+ console.log(`Web UI: http://localhost:${port}/`);
18
+
19
+ serve({
20
+ fetch: server.fetch,
21
+ port
22
+ }, (info) => {
23
+ console.log(`✓ Server is running at http://localhost:${info.port}`);
24
+ });
package/src/server.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { createToolServer } from "@vertesia/tools-sdk";
2
+ import { loadInteractions } from "./interactions/index.js";
3
+ import { skills } from "./skills/index.js";
4
+ import { tools } from "./tools/index.js";
5
+
6
+ const CONFIG__SERVER_TITLE = "Tool Server Template";
7
+
8
+ // Load interactions asynchronously
9
+ const interactions = await loadInteractions();
10
+
11
+ // Create server using tools-sdk
12
+ const server = createToolServer({
13
+ title: CONFIG__SERVER_TITLE,
14
+ prefix: '/api',
15
+ tools,
16
+ interactions,
17
+ skills,
18
+ // Uncomment and configure MCP providers if needed
19
+ // mcpProviders: []
20
+ });
21
+
22
+ export default server;
@@ -0,0 +1,59 @@
1
+ ---
2
+ name: code-review
3
+ title: Code Review Assistant
4
+ keywords: code, review, quality, best-practices, refactoring
5
+ ---
6
+
7
+ # Code Review Assistant
8
+
9
+ You are a code review assistant. Your role is to analyze code and provide constructive feedback focusing on:
10
+
11
+ ## Review Areas
12
+
13
+ 1. **Code Quality**
14
+ - Readability and maintainability
15
+ - Naming conventions
16
+ - Code organization and structure
17
+ - DRY (Don't Repeat Yourself) principle
18
+
19
+ 2. **Best Practices**
20
+ - Language-specific idioms and patterns
21
+ - Error handling
22
+ - Edge cases
23
+ - Security considerations
24
+
25
+ 3. **Performance**
26
+ - Algorithmic efficiency
27
+ - Resource usage
28
+ - Potential bottlenecks
29
+
30
+ 4. **Testing**
31
+ - Test coverage
32
+ - Test quality and clarity
33
+ - Missing test cases
34
+
35
+ ## Review Format
36
+
37
+ Structure your review as follows:
38
+
39
+ ### Strengths
40
+ List what the code does well.
41
+
42
+ ### Issues
43
+ For each issue found:
44
+ - **Severity**: Critical | Major | Minor
45
+ - **Location**: File and line number
46
+ - **Description**: What the issue is
47
+ - **Recommendation**: How to fix it
48
+ - **Example**: Show improved code if applicable
49
+
50
+ ### Summary
51
+ Provide an overall assessment and priority recommendations.
52
+
53
+ ## Guidelines
54
+
55
+ - Be constructive and respectful
56
+ - Explain the "why" behind suggestions
57
+ - Provide specific, actionable feedback
58
+ - Consider the context and requirements
59
+ - Balance perfection with pragmatism
@@ -0,0 +1,12 @@
1
+ import { SkillCollection, loadSkillsFromDirectory } from "@vertesia/tools-sdk";
2
+
3
+ export const CodeReviewSkills = new SkillCollection({
4
+ name: "code-review",
5
+ title: "Code Review Skills",
6
+ description: "Skills for reviewing and analyzing code quality",
7
+ skills: loadSkillsFromDirectory(new URL("./code-review", import.meta.url).pathname)
8
+ });
9
+
10
+ export const skills = [
11
+ CodeReviewSkills
12
+ ];
@@ -0,0 +1,60 @@
1
+ import { Tool, ToolExecutionContext, ToolExecutionPayload } from "@vertesia/tools-sdk";
2
+ import manifest from "./manifest.js";
3
+ import { ToolResultContent } from "@vertesia/common";
4
+
5
+ interface CalculatorParams {
6
+ expression: string;
7
+ }
8
+
9
+ /**
10
+ * Safely evaluates a mathematical expression
11
+ * Supports: +, -, *, /, ^, parentheses, and decimal numbers
12
+ */
13
+ function evaluateExpression(expr: string): number {
14
+ // Remove whitespace
15
+ expr = expr.replace(/\s+/g, '');
16
+
17
+ // Replace ^ with ** for exponentiation
18
+ expr = expr.replace(/\^/g, '**');
19
+
20
+ // Validate expression - only allow numbers, operators, parentheses, and decimal points
21
+ if (!/^[0-9+\-*/.()^]+$/.test(expr)) {
22
+ throw new Error('Invalid expression. Only numbers and operators (+, -, *, /, ^) are allowed.');
23
+ }
24
+
25
+ // Use Function constructor for safe evaluation (better than eval)
26
+ try {
27
+ const result = new Function(`'use strict'; return (${expr})`)();
28
+ if (typeof result !== 'number' || !isFinite(result)) {
29
+ throw new Error('Result is not a valid number');
30
+ }
31
+ return result;
32
+ } catch (error) {
33
+ throw new Error(`Failed to evaluate expression: ${error instanceof Error ? error.message : 'Unknown error'}`);
34
+ }
35
+ }
36
+
37
+ async function calculate(
38
+ payload: ToolExecutionPayload<CalculatorParams>,
39
+ _context: ToolExecutionContext
40
+ ): Promise<ToolResultContent> {
41
+ try {
42
+ const { expression } = payload.tool_use.tool_input!;
43
+ const result = evaluateExpression(expression);
44
+
45
+ return {
46
+ is_error: false,
47
+ content: `Result: ${expression} = ${result}`
48
+ } satisfies ToolResultContent;
49
+ } catch (error) {
50
+ return {
51
+ is_error: true,
52
+ content: `Calculation error: ${error instanceof Error ? error.message : 'Unknown error'}`
53
+ } satisfies ToolResultContent;
54
+ }
55
+ }
56
+
57
+ export const CalculatorTool = {
58
+ ...manifest,
59
+ run: calculate
60
+ } satisfies Tool<CalculatorParams>;
@@ -0,0 +1,7 @@
1
+ export default `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <rect x="4" y="2" width="16" height="20" rx="2"/>
3
+ <line x1="8" y1="6" x2="16" y2="6"/>
4
+ <line x1="8" y1="10" x2="16" y2="10"/>
5
+ <line x1="8" y1="14" x2="16" y2="14"/>
6
+ <line x1="8" y1="18" x2="16" y2="18"/>
7
+ </svg>`;
@@ -0,0 +1,11 @@
1
+ import { ToolCollection } from "@vertesia/tools-sdk";
2
+ import { CalculatorTool } from "./calculator.js";
3
+ import icon from "./icon.svg.js";
4
+
5
+ export const CalculatorTools = new ToolCollection({
6
+ name: "calculator",
7
+ title: "Calculator Tools",
8
+ description: "A collection of tools for performing mathematical calculations",
9
+ icon,
10
+ tools: [CalculatorTool]
11
+ });
@@ -0,0 +1,16 @@
1
+ import { ToolDefinition } from "@vertesia/tools-sdk";
2
+
3
+ export default {
4
+ name: "calculator",
5
+ description: "Performs basic mathematical calculations. Supports addition (+), subtraction (-), multiplication (*), division (/), and exponentiation (^).",
6
+ input_schema: {
7
+ type: "object",
8
+ properties: {
9
+ expression: {
10
+ type: "string",
11
+ description: "A mathematical expression to evaluate (e.g., '2 + 2', '10 * 5 - 3', '2^8')"
12
+ }
13
+ },
14
+ required: ["expression"]
15
+ }
16
+ } satisfies ToolDefinition;