create-web-ai-service 1.0.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.
- package/bin/cli.js +2 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +103 -0
- package/dist/plugins.d.ts +42 -0
- package/dist/plugins.js +64 -0
- package/dist/prompts.d.ts +14 -0
- package/dist/prompts.js +77 -0
- package/dist/templates.d.ts +10 -0
- package/dist/templates.js +107 -0
- package/package.json +43 -0
- package/templates/base/.env.example.ejs +22 -0
- package/templates/base/README.md.ejs +46 -0
- package/templates/base/package.json.ejs +25 -0
- package/templates/base/src/endpoints/hello/GET.yaml +23 -0
- package/templates/base/src/endpoints/hello/codes/format-greeting.ts +65 -0
- package/templates/base/src/endpoints/hello/prompts/greeting-system.txt +18 -0
- package/templates/base/tsconfig.json +28 -0
- package/templates/plugins/supabase/src/plugins/supabase.ts +93 -0
package/bin/cli.js
ADDED
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create Web AI Service CLI
|
|
3
|
+
*
|
|
4
|
+
* Main entry point for the scaffolder.
|
|
5
|
+
*/
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { execSync } from 'child_process';
|
|
8
|
+
import { promptForOptions } from './prompts.js';
|
|
9
|
+
import { generateProject } from './templates.js';
|
|
10
|
+
/**
|
|
11
|
+
* Print banner
|
|
12
|
+
*/
|
|
13
|
+
function printBanner() {
|
|
14
|
+
console.log('');
|
|
15
|
+
console.log(chalk.bold.cyan('╔════════════════════════════════════════════╗'));
|
|
16
|
+
console.log(chalk.bold.cyan('║') + chalk.bold.white(' 🚀 Create Web AI Service ') + chalk.bold.cyan('║'));
|
|
17
|
+
console.log(chalk.bold.cyan('║') + chalk.gray(' Build AI-powered APIs with YAML ') + chalk.bold.cyan('║'));
|
|
18
|
+
console.log(chalk.bold.cyan('╚════════════════════════════════════════════╝'));
|
|
19
|
+
console.log('');
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Parse command line arguments
|
|
23
|
+
*/
|
|
24
|
+
function parseArgs() {
|
|
25
|
+
const args = process.argv.slice(2);
|
|
26
|
+
const result = {};
|
|
27
|
+
// First non-flag argument is the project name
|
|
28
|
+
for (let i = 0; i < args.length; i++) {
|
|
29
|
+
const arg = args[i];
|
|
30
|
+
if (arg === '--plugins' && args[i + 1]) {
|
|
31
|
+
result.plugins = args[i + 1].split(',').map(s => s.trim());
|
|
32
|
+
i++;
|
|
33
|
+
}
|
|
34
|
+
else if (!arg.startsWith('-') && !result.projectName) {
|
|
35
|
+
result.projectName = arg;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Install dependencies using npm
|
|
42
|
+
*/
|
|
43
|
+
function installDependencies(projectPath) {
|
|
44
|
+
console.log('');
|
|
45
|
+
console.log(chalk.cyan('📦 Installing dependencies...'));
|
|
46
|
+
console.log('');
|
|
47
|
+
try {
|
|
48
|
+
execSync('npm install', {
|
|
49
|
+
cwd: projectPath,
|
|
50
|
+
stdio: 'inherit',
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.log(chalk.yellow('⚠️ Failed to install dependencies. Please run npm install manually.'));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Print success message with next steps
|
|
59
|
+
*/
|
|
60
|
+
function printSuccess(projectName) {
|
|
61
|
+
console.log('');
|
|
62
|
+
console.log(chalk.green('✅ Project created successfully!'));
|
|
63
|
+
console.log('');
|
|
64
|
+
console.log(chalk.bold('Next steps:'));
|
|
65
|
+
console.log('');
|
|
66
|
+
console.log(chalk.cyan(` cd ${projectName}`));
|
|
67
|
+
console.log(chalk.cyan(' cp .env.example .env'));
|
|
68
|
+
console.log(chalk.gray(' # Edit .env with your API keys'));
|
|
69
|
+
console.log(chalk.cyan(' npm run dev'));
|
|
70
|
+
console.log('');
|
|
71
|
+
console.log(chalk.gray('Then visit: ') + chalk.underline('http://localhost:3000/hello'));
|
|
72
|
+
console.log('');
|
|
73
|
+
console.log(chalk.bold('Documentation:'));
|
|
74
|
+
console.log(chalk.gray(' • Getting Started: ') + chalk.underline('https://github.com/yourrepo/web-ai-service#readme'));
|
|
75
|
+
console.log(chalk.gray(' • Creating Endpoints: see docs/creating-endpoints.md'));
|
|
76
|
+
console.log('');
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Main function
|
|
80
|
+
*/
|
|
81
|
+
async function main() {
|
|
82
|
+
printBanner();
|
|
83
|
+
const cliArgs = parseArgs();
|
|
84
|
+
// Get scaffold options (from prompts or CLI args)
|
|
85
|
+
const options = await promptForOptions(cliArgs.projectName);
|
|
86
|
+
// Override plugins if provided via CLI
|
|
87
|
+
if (cliArgs.plugins && cliArgs.plugins.length > 0) {
|
|
88
|
+
options.plugins = cliArgs.plugins;
|
|
89
|
+
}
|
|
90
|
+
console.log('');
|
|
91
|
+
console.log(chalk.cyan('🔧 Creating project...'));
|
|
92
|
+
// Generate project
|
|
93
|
+
await generateProject(options);
|
|
94
|
+
// Install dependencies
|
|
95
|
+
installDependencies(options.projectPath);
|
|
96
|
+
// Print success
|
|
97
|
+
printSuccess(options.projectName);
|
|
98
|
+
}
|
|
99
|
+
// Run
|
|
100
|
+
main().catch((error) => {
|
|
101
|
+
console.error(chalk.red('Error:'), error.message);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Registry
|
|
3
|
+
*
|
|
4
|
+
* Defines available plugins for the scaffolder.
|
|
5
|
+
* Add new plugins here to make them available in the CLI.
|
|
6
|
+
*/
|
|
7
|
+
export interface PluginEnvVar {
|
|
8
|
+
name: string;
|
|
9
|
+
description: string;
|
|
10
|
+
example: string;
|
|
11
|
+
}
|
|
12
|
+
export interface PluginConfig {
|
|
13
|
+
name: string;
|
|
14
|
+
description: string;
|
|
15
|
+
dependencies: Record<string, string>;
|
|
16
|
+
envVars: PluginEnvVar[];
|
|
17
|
+
files: {
|
|
18
|
+
source: string;
|
|
19
|
+
destination: string;
|
|
20
|
+
}[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Available plugins registry
|
|
24
|
+
* Add new plugins to this object to make them available in the CLI
|
|
25
|
+
*/
|
|
26
|
+
export declare const plugins: Record<string, PluginConfig>;
|
|
27
|
+
/**
|
|
28
|
+
* Get list of available plugin names
|
|
29
|
+
*/
|
|
30
|
+
export declare function getAvailablePlugins(): string[];
|
|
31
|
+
/**
|
|
32
|
+
* Get plugin config by name
|
|
33
|
+
*/
|
|
34
|
+
export declare function getPluginConfig(name: string): PluginConfig | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Get choices for inquirer checkbox
|
|
37
|
+
*/
|
|
38
|
+
export declare function getPluginChoices(): {
|
|
39
|
+
name: string;
|
|
40
|
+
value: string;
|
|
41
|
+
checked: boolean;
|
|
42
|
+
}[];
|
package/dist/plugins.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Registry
|
|
3
|
+
*
|
|
4
|
+
* Defines available plugins for the scaffolder.
|
|
5
|
+
* Add new plugins here to make them available in the CLI.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Available plugins registry
|
|
9
|
+
* Add new plugins to this object to make them available in the CLI
|
|
10
|
+
*/
|
|
11
|
+
export const plugins = {
|
|
12
|
+
supabase: {
|
|
13
|
+
name: 'Supabase',
|
|
14
|
+
description: 'Database & Auth integration with Supabase',
|
|
15
|
+
dependencies: {
|
|
16
|
+
'@supabase/supabase-js': '^2.90.1',
|
|
17
|
+
},
|
|
18
|
+
envVars: [
|
|
19
|
+
{
|
|
20
|
+
name: 'SUPABASE_URL',
|
|
21
|
+
description: 'Your Supabase project URL',
|
|
22
|
+
example: 'https://your-project.supabase.co',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'SUPABASE_ANON_KEY',
|
|
26
|
+
description: 'Your Supabase anon/public key',
|
|
27
|
+
example: 'your-supabase-anon-key',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'SUPABASE_SERVICE_KEY',
|
|
31
|
+
description: 'Your Supabase service role key (optional, for admin operations)',
|
|
32
|
+
example: 'your-supabase-service-key',
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
files: [
|
|
36
|
+
{
|
|
37
|
+
source: 'plugins/supabase/src/plugins/supabase.ts',
|
|
38
|
+
destination: 'src/plugins/supabase.ts',
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Get list of available plugin names
|
|
45
|
+
*/
|
|
46
|
+
export function getAvailablePlugins() {
|
|
47
|
+
return Object.keys(plugins);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Get plugin config by name
|
|
51
|
+
*/
|
|
52
|
+
export function getPluginConfig(name) {
|
|
53
|
+
return plugins[name];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get choices for inquirer checkbox
|
|
57
|
+
*/
|
|
58
|
+
export function getPluginChoices() {
|
|
59
|
+
return Object.entries(plugins).map(([key, plugin]) => ({
|
|
60
|
+
name: `${plugin.name} - ${plugin.description}`,
|
|
61
|
+
value: key,
|
|
62
|
+
checked: false,
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Prompts Module
|
|
3
|
+
*
|
|
4
|
+
* Defines interactive prompts for the scaffolder CLI.
|
|
5
|
+
*/
|
|
6
|
+
export interface ScaffoldOptions {
|
|
7
|
+
projectName: string;
|
|
8
|
+
projectPath: string;
|
|
9
|
+
plugins: string[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Run interactive prompts to gather scaffold options
|
|
13
|
+
*/
|
|
14
|
+
export declare function promptForOptions(initialName?: string): Promise<ScaffoldOptions>;
|
package/dist/prompts.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Prompts Module
|
|
3
|
+
*
|
|
4
|
+
* Defines interactive prompts for the scaffolder CLI.
|
|
5
|
+
*/
|
|
6
|
+
import inquirer from 'inquirer';
|
|
7
|
+
import { getPluginChoices } from './plugins.js';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import fs from 'fs-extra';
|
|
10
|
+
/**
|
|
11
|
+
* Validate project name
|
|
12
|
+
*/
|
|
13
|
+
function validateProjectName(input) {
|
|
14
|
+
if (!input || input.trim().length === 0) {
|
|
15
|
+
return 'Project name is required';
|
|
16
|
+
}
|
|
17
|
+
// Check for valid npm package name format
|
|
18
|
+
const validPattern = /^[a-z0-9]([a-z0-9-_.]*[a-z0-9])?$/;
|
|
19
|
+
if (!validPattern.test(input.toLowerCase())) {
|
|
20
|
+
return 'Project name must be lowercase and can only contain letters, numbers, hyphens, dots, and underscores';
|
|
21
|
+
}
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Check if directory exists
|
|
26
|
+
*/
|
|
27
|
+
async function checkDirectoryExists(projectPath) {
|
|
28
|
+
try {
|
|
29
|
+
await fs.access(projectPath);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Run interactive prompts to gather scaffold options
|
|
38
|
+
*/
|
|
39
|
+
export async function promptForOptions(initialName) {
|
|
40
|
+
const answers = await inquirer.prompt([
|
|
41
|
+
{
|
|
42
|
+
type: 'input',
|
|
43
|
+
name: 'projectName',
|
|
44
|
+
message: 'What is your project name?',
|
|
45
|
+
default: initialName || 'my-api',
|
|
46
|
+
validate: validateProjectName,
|
|
47
|
+
filter: (input) => input.trim().toLowerCase(),
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
type: 'checkbox',
|
|
51
|
+
name: 'plugins',
|
|
52
|
+
message: 'Which plugins would you like to install?',
|
|
53
|
+
choices: getPluginChoices(),
|
|
54
|
+
},
|
|
55
|
+
]);
|
|
56
|
+
const projectPath = path.resolve(process.cwd(), answers.projectName);
|
|
57
|
+
// Check if directory already exists
|
|
58
|
+
if (await checkDirectoryExists(projectPath)) {
|
|
59
|
+
const { overwrite } = await inquirer.prompt([
|
|
60
|
+
{
|
|
61
|
+
type: 'confirm',
|
|
62
|
+
name: 'overwrite',
|
|
63
|
+
message: `Directory ${answers.projectName} already exists. Overwrite?`,
|
|
64
|
+
default: false,
|
|
65
|
+
},
|
|
66
|
+
]);
|
|
67
|
+
if (!overwrite) {
|
|
68
|
+
console.log('Aborted. Please choose a different project name.');
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
projectName: answers.projectName,
|
|
74
|
+
projectPath,
|
|
75
|
+
plugins: answers.plugins,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Generation Module
|
|
3
|
+
*
|
|
4
|
+
* Handles copying and rendering template files.
|
|
5
|
+
*/
|
|
6
|
+
import { ScaffoldOptions } from './prompts.js';
|
|
7
|
+
/**
|
|
8
|
+
* Generate project from templates
|
|
9
|
+
*/
|
|
10
|
+
export declare function generateProject(options: ScaffoldOptions): Promise<void>;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Generation Module
|
|
3
|
+
*
|
|
4
|
+
* Handles copying and rendering template files.
|
|
5
|
+
*/
|
|
6
|
+
import fs from 'fs-extra';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import ejs from 'ejs';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import { getPluginConfig } from './plugins.js';
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = path.dirname(__filename);
|
|
13
|
+
// Templates are in the parent directory relative to dist/
|
|
14
|
+
const TEMPLATES_DIR = path.resolve(__dirname, '..', 'templates');
|
|
15
|
+
/**
|
|
16
|
+
* Build template data from scaffold options
|
|
17
|
+
*/
|
|
18
|
+
function buildTemplateData(options) {
|
|
19
|
+
const dependencies = {
|
|
20
|
+
'web-ai-service': '^1.0.0',
|
|
21
|
+
};
|
|
22
|
+
const devDependencies = {
|
|
23
|
+
'@types/node': '^20.11.5',
|
|
24
|
+
'typescript': '^5.3.3',
|
|
25
|
+
'tsx': '^4.7.0',
|
|
26
|
+
};
|
|
27
|
+
const envVars = [];
|
|
28
|
+
// Add plugin dependencies and env vars
|
|
29
|
+
for (const pluginName of options.plugins) {
|
|
30
|
+
const plugin = getPluginConfig(pluginName);
|
|
31
|
+
if (plugin) {
|
|
32
|
+
Object.assign(dependencies, plugin.dependencies);
|
|
33
|
+
envVars.push(...plugin.envVars);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
projectName: options.projectName,
|
|
38
|
+
plugins: options.plugins,
|
|
39
|
+
dependencies,
|
|
40
|
+
devDependencies,
|
|
41
|
+
envVars,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Copy and render base template files
|
|
46
|
+
*/
|
|
47
|
+
async function copyBaseTemplate(options, data) {
|
|
48
|
+
const baseDir = path.join(TEMPLATES_DIR, 'base');
|
|
49
|
+
// Get all files in base template
|
|
50
|
+
const files = await fs.readdir(baseDir, { recursive: true });
|
|
51
|
+
for (const file of files) {
|
|
52
|
+
const sourcePath = path.join(baseDir, file.toString());
|
|
53
|
+
const stat = await fs.stat(sourcePath);
|
|
54
|
+
if (stat.isDirectory()) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
// Determine destination path (remove .ejs extension if present)
|
|
58
|
+
let destFile = file.toString();
|
|
59
|
+
const isEjsTemplate = destFile.endsWith('.ejs');
|
|
60
|
+
if (isEjsTemplate) {
|
|
61
|
+
destFile = destFile.slice(0, -4); // Remove .ejs
|
|
62
|
+
}
|
|
63
|
+
const destPath = path.join(options.projectPath, destFile);
|
|
64
|
+
// Ensure destination directory exists
|
|
65
|
+
await fs.ensureDir(path.dirname(destPath));
|
|
66
|
+
if (isEjsTemplate) {
|
|
67
|
+
// Render EJS template
|
|
68
|
+
const content = await fs.readFile(sourcePath, 'utf-8');
|
|
69
|
+
const rendered = ejs.render(content, data);
|
|
70
|
+
await fs.writeFile(destPath, rendered);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
// Copy file as-is
|
|
74
|
+
await fs.copy(sourcePath, destPath);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Copy plugin files
|
|
80
|
+
*/
|
|
81
|
+
async function copyPluginFiles(options) {
|
|
82
|
+
for (const pluginName of options.plugins) {
|
|
83
|
+
const plugin = getPluginConfig(pluginName);
|
|
84
|
+
if (!plugin)
|
|
85
|
+
continue;
|
|
86
|
+
for (const fileMapping of plugin.files) {
|
|
87
|
+
const sourcePath = path.join(TEMPLATES_DIR, fileMapping.source);
|
|
88
|
+
const destPath = path.join(options.projectPath, fileMapping.destination);
|
|
89
|
+
// Ensure destination directory exists
|
|
90
|
+
await fs.ensureDir(path.dirname(destPath));
|
|
91
|
+
// Copy file
|
|
92
|
+
await fs.copy(sourcePath, destPath);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Generate project from templates
|
|
98
|
+
*/
|
|
99
|
+
export async function generateProject(options) {
|
|
100
|
+
const data = buildTemplateData(options);
|
|
101
|
+
// Ensure project directory exists (clean if exists)
|
|
102
|
+
await fs.ensureDir(options.projectPath);
|
|
103
|
+
// Copy base template
|
|
104
|
+
await copyBaseTemplate(options, data);
|
|
105
|
+
// Copy plugin files
|
|
106
|
+
await copyPluginFiles(options);
|
|
107
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-web-ai-service",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI scaffolder for creating new web-ai-service projects",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"create-web-ai-service": "./bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"bin",
|
|
13
|
+
"templates"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"dev": "tsc --watch",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"cli",
|
|
22
|
+
"scaffolder",
|
|
23
|
+
"web-ai-service",
|
|
24
|
+
"api",
|
|
25
|
+
"workflow",
|
|
26
|
+
"llm"
|
|
27
|
+
],
|
|
28
|
+
"author": "",
|
|
29
|
+
"license": "ISC",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"chalk": "^5.3.0",
|
|
32
|
+
"ejs": "^3.1.10",
|
|
33
|
+
"inquirer": "^9.2.12",
|
|
34
|
+
"fs-extra": "^11.2.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/ejs": "^3.1.5",
|
|
38
|
+
"@types/fs-extra": "^11.0.4",
|
|
39
|
+
"@types/inquirer": "^9.0.7",
|
|
40
|
+
"@types/node": "^20.11.5",
|
|
41
|
+
"typescript": "^5.3.3"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Server Configuration
|
|
2
|
+
PORT=3000
|
|
3
|
+
|
|
4
|
+
# LLM Provider API Keys (add keys for providers you want to use)
|
|
5
|
+
GEMINI_API_KEY=your-gemini-api-key-here
|
|
6
|
+
GROK_API_KEY=your-grok-api-key-here
|
|
7
|
+
OPENAI_API_KEY=your-openai-api-key-here
|
|
8
|
+
ANTHROPIC_API_KEY=your-anthropic-api-key-here
|
|
9
|
+
|
|
10
|
+
# LLM Configuration
|
|
11
|
+
LLM_TIMEOUT_MS=30000
|
|
12
|
+
|
|
13
|
+
# Logging
|
|
14
|
+
LOG_LEVEL=info
|
|
15
|
+
<% if (envVars.length > 0) { %>
|
|
16
|
+
|
|
17
|
+
# Plugin Configuration
|
|
18
|
+
<% for (const envVar of envVars) { %>
|
|
19
|
+
# <%= envVar.description %>
|
|
20
|
+
<%= envVar.name %>=<%= envVar.example %>
|
|
21
|
+
<% } %>
|
|
22
|
+
<% } %>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# <%= projectName %>
|
|
2
|
+
|
|
3
|
+
An AI-powered API service built with [web-ai-service](https://github.com/yourrepo/web-ai-service).
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Install dependencies
|
|
9
|
+
npm install
|
|
10
|
+
|
|
11
|
+
# Configure environment
|
|
12
|
+
cp .env.example .env
|
|
13
|
+
# Edit .env with your API keys
|
|
14
|
+
|
|
15
|
+
# Start development server
|
|
16
|
+
npm run dev
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Server starts at `http://localhost:3000`.
|
|
20
|
+
|
|
21
|
+
## Project Structure
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
<%= projectName %>/
|
|
25
|
+
├── src/
|
|
26
|
+
│ ├── endpoints/ # API endpoint definitions
|
|
27
|
+
│ │ └── hello/ # Example endpoint → GET /hello
|
|
28
|
+
│ │ ├── GET.yaml # Workflow definition
|
|
29
|
+
│ │ ├── codes/ # TypeScript code nodes
|
|
30
|
+
│ │ └── prompts/ # LLM system prompts
|
|
31
|
+
│ └── plugins/ # Shared code modules
|
|
32
|
+
├── .env # Environment configuration
|
|
33
|
+
└── package.json
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Creating Endpoints
|
|
37
|
+
|
|
38
|
+
Each folder in `src/endpoints/` becomes an API route. Create a YAML file named after the HTTP method (e.g.,
|
|
39
|
+
`POST.yaml`, `GET.yaml`).
|
|
40
|
+
|
|
41
|
+
See the `hello` endpoint for an example.
|
|
42
|
+
|
|
43
|
+
## Learn More
|
|
44
|
+
|
|
45
|
+
- [Web AI Service Documentation](https://github.com/yourrepo/web-ai-service)
|
|
46
|
+
- [Creating Endpoints Guide](./docs/creating-endpoints.md)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "<%= projectName %>",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "An AI-powered API service built with web-ai-service",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "web-ai-service",
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"start": "node dist/engine/cli.js",
|
|
10
|
+
"validate": "web-ai-service validate"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"api",
|
|
14
|
+
"ai",
|
|
15
|
+
"workflow"
|
|
16
|
+
],
|
|
17
|
+
"author": "",
|
|
18
|
+
"license": "ISC",
|
|
19
|
+
"dependencies": {
|
|
20
|
+
<%- Object.entries(dependencies).map(([name, version]) => ` "${name}": "${version}"`).join(',\n') %>
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
<%- Object.entries(devDependencies).map(([name, version]) => ` "${name}": "${version}"`).join(',\n') %>
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
version: "1.0"
|
|
2
|
+
|
|
3
|
+
stages:
|
|
4
|
+
- name: process
|
|
5
|
+
nodes:
|
|
6
|
+
# Code node to transform and validate the request
|
|
7
|
+
format_greeting:
|
|
8
|
+
type: code
|
|
9
|
+
input: $input
|
|
10
|
+
file: format-greeting.ts
|
|
11
|
+
|
|
12
|
+
- name: respond
|
|
13
|
+
nodes:
|
|
14
|
+
# LLM node to generate a personalized greeting
|
|
15
|
+
greeting:
|
|
16
|
+
type: llm
|
|
17
|
+
input: process.format_greeting
|
|
18
|
+
provider: gemini
|
|
19
|
+
model: gemini-2.0-flash-lite
|
|
20
|
+
temperature: 0.7
|
|
21
|
+
maxTokens: 256
|
|
22
|
+
systemMessages:
|
|
23
|
+
- file: greeting-system.txt
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format Greeting Code Node
|
|
3
|
+
*
|
|
4
|
+
* This code node demonstrates input processing before passing to an LLM.
|
|
5
|
+
* It extracts and validates the name parameter, then formats the request
|
|
6
|
+
* for the greeting LLM node.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { NodeOutput } from '@workflow/types';
|
|
10
|
+
|
|
11
|
+
interface HelloInput {
|
|
12
|
+
name?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface FormattedRequest {
|
|
16
|
+
name: string;
|
|
17
|
+
timestamp: string;
|
|
18
|
+
greeting_type: 'morning' | 'afternoon' | 'evening';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Determine greeting type based on current time
|
|
23
|
+
*/
|
|
24
|
+
function getGreetingType(): 'morning' | 'afternoon' | 'evening' {
|
|
25
|
+
const hour = new Date().getHours();
|
|
26
|
+
|
|
27
|
+
if (hour < 12) {
|
|
28
|
+
return 'morning';
|
|
29
|
+
} else if (hour < 17) {
|
|
30
|
+
return 'afternoon';
|
|
31
|
+
} else {
|
|
32
|
+
return 'evening';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Main code node function
|
|
38
|
+
*
|
|
39
|
+
* @param input - The raw request input
|
|
40
|
+
* @returns Formatted request for the LLM node
|
|
41
|
+
*/
|
|
42
|
+
export default async function (input: unknown): Promise<NodeOutput> {
|
|
43
|
+
const data = input as HelloInput;
|
|
44
|
+
|
|
45
|
+
// Extract and validate name (default to 'World')
|
|
46
|
+
const name = data.name?.trim() || 'World';
|
|
47
|
+
|
|
48
|
+
// Validate name length
|
|
49
|
+
if (name.length > 100) {
|
|
50
|
+
throw new Error('Name must be 100 characters or less');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Build formatted request
|
|
54
|
+
const formatted: FormattedRequest = {
|
|
55
|
+
name,
|
|
56
|
+
timestamp: new Date().toISOString(),
|
|
57
|
+
greeting_type: getGreetingType(),
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Return as JSON output for the next node
|
|
61
|
+
return {
|
|
62
|
+
type: 'json',
|
|
63
|
+
value: formatted,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
You are a friendly greeting assistant. Generate warm, personalized greetings.
|
|
2
|
+
|
|
3
|
+
You will receive a JSON object with:
|
|
4
|
+
- name: The person's name to greet
|
|
5
|
+
- timestamp: Current ISO timestamp
|
|
6
|
+
- greeting_type: Either 'morning', 'afternoon', or 'evening'
|
|
7
|
+
|
|
8
|
+
Generate a friendly, contextual greeting that:
|
|
9
|
+
1. Uses the appropriate time-of-day salutation (Good morning/afternoon/evening)
|
|
10
|
+
2. Addresses the person by name
|
|
11
|
+
3. Adds a brief, positive message appropriate to the time of day
|
|
12
|
+
4. Keeps the total response under 2 sentences
|
|
13
|
+
|
|
14
|
+
Example input:
|
|
15
|
+
{"name": "Alice", "timestamp": "2024-01-15T09:30:00Z", "greeting_type": "morning"}
|
|
16
|
+
|
|
17
|
+
Example output:
|
|
18
|
+
Good morning, Alice! Hope you have a wonderful and productive day ahead.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"paths": {
|
|
13
|
+
"@workflow/types": [
|
|
14
|
+
"./node_modules/web-ai-service/dist/engine/types/index.js"
|
|
15
|
+
],
|
|
16
|
+
"@code-plugins/*": [
|
|
17
|
+
"./src/plugins/*"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"include": [
|
|
22
|
+
"src/**/*"
|
|
23
|
+
],
|
|
24
|
+
"exclude": [
|
|
25
|
+
"node_modules",
|
|
26
|
+
"dist"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supabase Plugin
|
|
3
|
+
*
|
|
4
|
+
* Provides a configured Supabase client for use in code nodes.
|
|
5
|
+
*
|
|
6
|
+
* Usage in code nodes:
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { supabase, getSupabaseClient } from '@code-plugins/supabase.js';
|
|
9
|
+
*
|
|
10
|
+
* // Use the default client (reads from env vars)
|
|
11
|
+
* const { data, error } = await supabase.from('table').select('*');
|
|
12
|
+
*
|
|
13
|
+
* // Or create a custom client
|
|
14
|
+
* const client = getSupabaseClient('https://custom.supabase.co', 'custom-key');
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* Required environment variables:
|
|
18
|
+
* - SUPABASE_URL: Your Supabase project URL
|
|
19
|
+
* - SUPABASE_ANON_KEY: Your Supabase anon/public key
|
|
20
|
+
* - SUPABASE_SERVICE_KEY: (Optional) Service role key for admin operations
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { createClient, SupabaseClient } from '@supabase/supabase-js';
|
|
24
|
+
|
|
25
|
+
// Default client instance (lazy initialized)
|
|
26
|
+
let defaultClient: SupabaseClient | null = null;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get the Supabase URL from environment
|
|
30
|
+
*/
|
|
31
|
+
function getSupabaseUrl(): string {
|
|
32
|
+
const url = process.env.SUPABASE_URL;
|
|
33
|
+
if (!url) {
|
|
34
|
+
throw new Error('SUPABASE_URL environment variable is required');
|
|
35
|
+
}
|
|
36
|
+
return url;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get the Supabase key from environment
|
|
41
|
+
* Prefers service key for server-side operations, falls back to anon key
|
|
42
|
+
*/
|
|
43
|
+
function getSupabaseKey(): string {
|
|
44
|
+
const serviceKey = process.env.SUPABASE_SERVICE_KEY;
|
|
45
|
+
const anonKey = process.env.SUPABASE_ANON_KEY;
|
|
46
|
+
|
|
47
|
+
if (serviceKey) {
|
|
48
|
+
return serviceKey;
|
|
49
|
+
}
|
|
50
|
+
if (anonKey) {
|
|
51
|
+
return anonKey;
|
|
52
|
+
}
|
|
53
|
+
throw new Error('SUPABASE_SERVICE_KEY or SUPABASE_ANON_KEY environment variable is required');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Create a new Supabase client with custom URL and key
|
|
58
|
+
*/
|
|
59
|
+
export function getSupabaseClient(url?: string, key?: string): SupabaseClient {
|
|
60
|
+
const supabaseUrl = url || getSupabaseUrl();
|
|
61
|
+
const supabaseKey = key || getSupabaseKey();
|
|
62
|
+
|
|
63
|
+
return createClient(supabaseUrl, supabaseKey, {
|
|
64
|
+
auth: {
|
|
65
|
+
autoRefreshToken: false,
|
|
66
|
+
persistSession: false,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get the default Supabase client (singleton)
|
|
73
|
+
* Uses environment variables for configuration
|
|
74
|
+
*/
|
|
75
|
+
export function getDefaultClient(): SupabaseClient {
|
|
76
|
+
if (!defaultClient) {
|
|
77
|
+
defaultClient = getSupabaseClient();
|
|
78
|
+
}
|
|
79
|
+
return defaultClient;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Default Supabase client instance
|
|
84
|
+
* Lazy-loaded on first access
|
|
85
|
+
*/
|
|
86
|
+
export const supabase: SupabaseClient = new Proxy({} as SupabaseClient, {
|
|
87
|
+
get(_target, prop) {
|
|
88
|
+
return Reflect.get(getDefaultClient(), prop);
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Re-export useful types
|
|
93
|
+
export type { SupabaseClient } from '@supabase/supabase-js';
|