@claudetools/tools 0.3.8 → 0.4.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.
@@ -0,0 +1,214 @@
1
+ // =============================================================================
2
+ // Template Registry Client
3
+ // =============================================================================
4
+ //
5
+ // HTTP client for Cloudflare Workers template registry with local caching
6
+ // and fallback to bundled templates on network failure.
7
+ //
8
+ import fs from 'fs/promises';
9
+ import path from 'path';
10
+ import { fileURLToPath } from 'url';
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
+ export class TemplateRegistry {
13
+ baseUrl;
14
+ cacheDir;
15
+ useCache;
16
+ constructor(baseUrl = 'https://templates.claudetools.com', useCache = true) {
17
+ this.baseUrl = baseUrl;
18
+ this.cacheDir = path.join(__dirname, '../../templates');
19
+ this.useCache = useCache;
20
+ }
21
+ /**
22
+ * List all available generators
23
+ */
24
+ async listGenerators() {
25
+ try {
26
+ const response = await fetch(`${this.baseUrl}/registry`, {
27
+ signal: AbortSignal.timeout(5000), // 5 second timeout
28
+ });
29
+ if (!response.ok) {
30
+ throw new Error(`Registry fetch failed: ${response.statusText}`);
31
+ }
32
+ const data = await response.json();
33
+ return data.generators;
34
+ }
35
+ catch (error) {
36
+ console.warn('Failed to fetch from registry, using local fallback:', error);
37
+ return this.getLocalGenerators();
38
+ }
39
+ }
40
+ /**
41
+ * Get metadata for specific generator
42
+ */
43
+ async getGeneratorMetadata(generatorId) {
44
+ try {
45
+ const response = await fetch(`${this.baseUrl}/metadata/${generatorId}`, {
46
+ signal: AbortSignal.timeout(5000),
47
+ });
48
+ if (!response.ok) {
49
+ throw new Error(`Metadata fetch failed: ${response.statusText}`);
50
+ }
51
+ return await response.json();
52
+ }
53
+ catch (error) {
54
+ console.warn('Failed to fetch metadata from registry, using local fallback:', error);
55
+ return this.getLocalMetadata(generatorId);
56
+ }
57
+ }
58
+ /**
59
+ * Get template file content
60
+ */
61
+ async getTemplate(generatorId, templateFile) {
62
+ // Check cache first
63
+ if (this.useCache) {
64
+ const cached = await this.getCachedTemplate(generatorId, templateFile);
65
+ if (cached)
66
+ return cached;
67
+ }
68
+ // Fetch from registry
69
+ try {
70
+ const response = await fetch(`${this.baseUrl}/templates/${generatorId}/${templateFile}`, {
71
+ signal: AbortSignal.timeout(10000), // 10 second timeout for templates
72
+ });
73
+ if (!response.ok) {
74
+ throw new Error(`Template fetch failed: ${response.statusText}`);
75
+ }
76
+ const content = await response.text();
77
+ // Cache it
78
+ if (this.useCache) {
79
+ await this.cacheTemplate(generatorId, templateFile, content);
80
+ }
81
+ return content;
82
+ }
83
+ catch (error) {
84
+ console.warn('Failed to fetch template from registry, using local fallback:', error);
85
+ return this.getLocalTemplate(generatorId, templateFile);
86
+ }
87
+ }
88
+ /**
89
+ * Get multiple templates at once
90
+ */
91
+ async getTemplates(generatorId, templateFiles) {
92
+ const results = {};
93
+ await Promise.all(templateFiles.map(async (file) => {
94
+ results[file] = await this.getTemplate(generatorId, file);
95
+ }));
96
+ return results;
97
+ }
98
+ /**
99
+ * Check if template is cached locally
100
+ */
101
+ async getCachedTemplate(generatorId, templateFile) {
102
+ try {
103
+ const cachePath = path.join(this.cacheDir, generatorId, templateFile);
104
+ return await fs.readFile(cachePath, 'utf-8');
105
+ }
106
+ catch {
107
+ return null;
108
+ }
109
+ }
110
+ /**
111
+ * Cache template locally
112
+ */
113
+ async cacheTemplate(generatorId, templateFile, content) {
114
+ try {
115
+ const cachePath = path.join(this.cacheDir, generatorId, templateFile);
116
+ await fs.mkdir(path.dirname(cachePath), { recursive: true });
117
+ await fs.writeFile(cachePath, content, 'utf-8');
118
+ }
119
+ catch (error) {
120
+ console.warn('Failed to cache template:', error);
121
+ }
122
+ }
123
+ /**
124
+ * Get local generators (fallback)
125
+ */
126
+ async getLocalGenerators() {
127
+ // Return hardcoded list of bundled generators
128
+ return [
129
+ {
130
+ id: 'express-api',
131
+ name: 'Express REST API',
132
+ framework: 'express',
133
+ description: 'Generate TypeScript Express API with CRUD operations',
134
+ features: ['auth', 'validation', 'tests'],
135
+ databases: ['postgresql', 'mysql', 'mongodb'],
136
+ version: '1.0.0',
137
+ templates: [
138
+ 'controller.ts.j2',
139
+ 'model.ts.j2',
140
+ 'route.ts.j2',
141
+ 'middleware.ts.j2',
142
+ 'validator.ts.j2',
143
+ ],
144
+ },
145
+ ];
146
+ }
147
+ /**
148
+ * Get local metadata (fallback)
149
+ */
150
+ async getLocalMetadata(generatorId) {
151
+ const generators = await this.getLocalGenerators();
152
+ const generator = generators.find(g => g.id === generatorId);
153
+ if (!generator) {
154
+ throw new Error(`Generator not found: ${generatorId}`);
155
+ }
156
+ return generator;
157
+ }
158
+ /**
159
+ * Get local template (fallback)
160
+ */
161
+ async getLocalTemplate(generatorId, templateFile) {
162
+ // Try to load from local bundled templates
163
+ const localPath = path.join(__dirname, '../../../cloudflare/templates', generatorId, templateFile);
164
+ try {
165
+ return await fs.readFile(localPath, 'utf-8');
166
+ }
167
+ catch (error) {
168
+ throw new Error(`Template not found: ${generatorId}/${templateFile}. Registry unavailable and local fallback missing.`);
169
+ }
170
+ }
171
+ /**
172
+ * Clear local cache
173
+ */
174
+ async clearCache() {
175
+ try {
176
+ await fs.rm(this.cacheDir, { recursive: true, force: true });
177
+ }
178
+ catch (error) {
179
+ console.warn('Failed to clear cache:', error);
180
+ }
181
+ }
182
+ /**
183
+ * Get cache statistics
184
+ */
185
+ async getCacheStats() {
186
+ try {
187
+ const entries = await fs.readdir(this.cacheDir, { withFileTypes: true });
188
+ const generators = entries.filter(e => e.isDirectory()).map(e => e.name);
189
+ let totalFiles = 0;
190
+ let totalSize = 0;
191
+ for (const gen of generators) {
192
+ const genPath = path.join(this.cacheDir, gen);
193
+ const files = await fs.readdir(genPath);
194
+ totalFiles += files.length;
195
+ for (const file of files) {
196
+ const stats = await fs.stat(path.join(genPath, file));
197
+ totalSize += stats.size;
198
+ }
199
+ }
200
+ return {
201
+ cachedGenerators: generators,
202
+ totalFiles,
203
+ totalSize,
204
+ };
205
+ }
206
+ catch {
207
+ return {
208
+ cachedGenerators: [],
209
+ totalFiles: 0,
210
+ totalSize: 0,
211
+ };
212
+ }
213
+ }
214
+ }
@@ -0,0 +1,17 @@
1
+ import { EntitySpec } from './parser.js';
2
+ export declare class TemplateEngine {
3
+ private env;
4
+ constructor();
5
+ /**
6
+ * Render template string with context
7
+ */
8
+ render(template: string, context: any): string;
9
+ /**
10
+ * Add custom filters for code generation
11
+ */
12
+ private addFilters;
13
+ }
14
+ /**
15
+ * Helper to build template context from EntitySpec
16
+ */
17
+ export declare function buildContext(entity: EntitySpec, options?: any): any;
@@ -0,0 +1,149 @@
1
+ // =============================================================================
2
+ // Template Engine with Custom Filters
3
+ // =============================================================================
4
+ //
5
+ // Jinja2-style template engine using nunjucks with custom filters for
6
+ // code generation (plural, camelCase, TypeScript types, SQL types, etc.)
7
+ //
8
+ import nunjucks from 'nunjucks';
9
+ export class TemplateEngine {
10
+ env;
11
+ constructor() {
12
+ // Configure nunjucks
13
+ this.env = new nunjucks.Environment(null, {
14
+ autoescape: false,
15
+ trimBlocks: true,
16
+ lstripBlocks: true,
17
+ });
18
+ // Add custom filters
19
+ this.addFilters();
20
+ }
21
+ /**
22
+ * Render template string with context
23
+ */
24
+ render(template, context) {
25
+ return this.env.renderString(template, context);
26
+ }
27
+ /**
28
+ * Add custom filters for code generation
29
+ */
30
+ addFilters() {
31
+ // Pluralize: User → Users
32
+ this.env.addFilter('plural', (str) => {
33
+ if (str.endsWith('y'))
34
+ return str.slice(0, -1) + 'ies';
35
+ if (str.endsWith('s'))
36
+ return str + 'es';
37
+ return str + 's';
38
+ });
39
+ // Lowercase: User → user
40
+ this.env.addFilter('lower', (str) => {
41
+ return str.toLowerCase();
42
+ });
43
+ // Uppercase: user → USER
44
+ this.env.addFilter('upper', (str) => {
45
+ return str.toUpperCase();
46
+ });
47
+ // camelCase: user_name → userName
48
+ this.env.addFilter('camel', (str) => {
49
+ return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
50
+ });
51
+ // PascalCase: user_name → UserName
52
+ this.env.addFilter('pascal', (str) => {
53
+ const camel = str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
54
+ return camel.charAt(0).toUpperCase() + camel.slice(1);
55
+ });
56
+ // snake_case: userName → user_name
57
+ this.env.addFilter('snake', (str) => {
58
+ return str.replace(/[A-Z]/g, letter => '_' + letter.toLowerCase());
59
+ });
60
+ // kebab-case: userName → user-name
61
+ this.env.addFilter('kebab', (str) => {
62
+ return str.replace(/[A-Z]/g, letter => '-' + letter.toLowerCase());
63
+ });
64
+ // Check if array includes value
65
+ this.env.addFilter('includes', (arr, val) => {
66
+ return Array.isArray(arr) && arr.includes(val);
67
+ });
68
+ // Get TypeScript type for field
69
+ this.env.addFilter('tsType', (field) => {
70
+ if (field.type.kind === 'primitive') {
71
+ const typeMap = {
72
+ string: 'string',
73
+ integer: 'number',
74
+ decimal: 'number',
75
+ boolean: 'boolean',
76
+ datetime: 'Date',
77
+ };
78
+ return typeMap[field.type.value];
79
+ }
80
+ if (field.type.kind === 'reference') {
81
+ return field.type.entity;
82
+ }
83
+ if (field.type.kind === 'enum') {
84
+ return field.type.values.map(v => `'${v}'`).join(' | ');
85
+ }
86
+ return 'any';
87
+ });
88
+ // Get SQL type for field
89
+ this.env.addFilter('sqlType', (field) => {
90
+ if (field.type.kind === 'primitive') {
91
+ const typeMap = {
92
+ string: 'VARCHAR(255)',
93
+ integer: 'INTEGER',
94
+ decimal: 'DECIMAL(10,2)',
95
+ boolean: 'BOOLEAN',
96
+ datetime: 'TIMESTAMP',
97
+ };
98
+ return typeMap[field.type.value];
99
+ }
100
+ if (field.type.kind === 'reference') {
101
+ return 'INTEGER'; // Foreign key
102
+ }
103
+ if (field.type.kind === 'enum') {
104
+ return `ENUM(${field.type.values.map(v => `'${v}'`).join(',')})`;
105
+ }
106
+ return 'TEXT';
107
+ });
108
+ // Check if field has specific constraint
109
+ this.env.addFilter('hasConstraint', (field, constraintKind) => {
110
+ return field.constraints.some(c => c.kind === constraintKind);
111
+ });
112
+ // Get constraint value (for min, max, default)
113
+ this.env.addFilter('getConstraint', (field, constraintKind) => {
114
+ const constraint = field.constraints.find(c => c.kind === constraintKind);
115
+ if (!constraint)
116
+ return null;
117
+ if ('value' in constraint)
118
+ return constraint.value;
119
+ return true;
120
+ });
121
+ // Join array with custom separator
122
+ this.env.addFilter('joinWith', (arr, separator) => {
123
+ return arr.join(separator);
124
+ });
125
+ // First letter uppercase
126
+ this.env.addFilter('capitalize', (str) => {
127
+ return str.charAt(0).toUpperCase() + str.slice(1);
128
+ });
129
+ // Indent lines
130
+ this.env.addFilter('indent', (str, spaces = 2) => {
131
+ const indent = ' '.repeat(spaces);
132
+ return str.split('\n').map(line => indent + line).join('\n');
133
+ });
134
+ }
135
+ }
136
+ /**
137
+ * Helper to build template context from EntitySpec
138
+ */
139
+ export function buildContext(entity, options = {}) {
140
+ return {
141
+ entity,
142
+ options,
143
+ // Helper data
144
+ hasRequiredFields: entity.fields.some(f => f.constraints.some(c => c.kind === 'required')),
145
+ hasUniqueFields: entity.fields.some(f => f.constraints.some(c => c.kind === 'unique')),
146
+ hasHashedFields: entity.fields.some(f => f.constraints.some(c => c.kind === 'hashed')),
147
+ hasReferences: entity.fields.some(f => f.type.kind === 'reference'),
148
+ };
149
+ }
@@ -0,0 +1,64 @@
1
+ import { EntitySpec } from './parser.js';
2
+ /**
3
+ * Options for API generation
4
+ */
5
+ export interface GenerateApiOptions {
6
+ auth?: boolean;
7
+ validation?: boolean;
8
+ tests?: boolean;
9
+ database?: 'postgresql' | 'mysql' | 'mongodb';
10
+ }
11
+ /**
12
+ * Options for frontend generation
13
+ */
14
+ export interface GenerateFrontendOptions {
15
+ ui?: 'shadcn' | 'mui' | 'chakra';
16
+ forms?: boolean;
17
+ tables?: boolean;
18
+ routing?: boolean;
19
+ }
20
+ /**
21
+ * Options for component generation
22
+ */
23
+ export interface GenerateComponentOptions {
24
+ ui?: 'shadcn' | 'mui' | 'chakra';
25
+ validation?: boolean;
26
+ }
27
+ /**
28
+ * Generated code result
29
+ */
30
+ export interface GenerationResult {
31
+ files: Record<string, string>;
32
+ metadata: GenerationMetadata;
33
+ }
34
+ /**
35
+ * Metadata about generated code
36
+ */
37
+ export interface GenerationMetadata {
38
+ generator: string;
39
+ framework: string;
40
+ entities: string[];
41
+ filesGenerated: number;
42
+ linesOfCode: number;
43
+ estimatedTokensSaved: number;
44
+ }
45
+ /**
46
+ * Generator metadata from registry
47
+ */
48
+ export interface GeneratorMetadata {
49
+ id: string;
50
+ name: string;
51
+ framework: string;
52
+ description: string;
53
+ features: string[];
54
+ databases?: string[];
55
+ version: string;
56
+ templates: string[];
57
+ }
58
+ /**
59
+ * Template context for rendering
60
+ */
61
+ export interface TemplateContext {
62
+ entity: EntitySpec;
63
+ options: any;
64
+ }
@@ -0,0 +1,4 @@
1
+ // =============================================================================
2
+ // CodeDNA TypeScript Types
3
+ // =============================================================================
4
+ export {};
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Handle codedna_generate_api tool call
3
+ */
4
+ export declare function handleGenerateApi(args: any): Promise<{
5
+ error: string;
6
+ details: string[] | undefined;
7
+ message?: undefined;
8
+ supported?: undefined;
9
+ success?: undefined;
10
+ files?: undefined;
11
+ metadata?: undefined;
12
+ tokenSavings?: undefined;
13
+ } | {
14
+ error: string;
15
+ message: string;
16
+ details?: undefined;
17
+ supported?: undefined;
18
+ success?: undefined;
19
+ files?: undefined;
20
+ metadata?: undefined;
21
+ tokenSavings?: undefined;
22
+ } | {
23
+ error: string;
24
+ supported: string[];
25
+ details?: undefined;
26
+ message?: undefined;
27
+ success?: undefined;
28
+ files?: undefined;
29
+ metadata?: undefined;
30
+ tokenSavings?: undefined;
31
+ } | {
32
+ success: boolean;
33
+ files: Record<string, string>;
34
+ metadata: import("../codedna/types.js").GenerationMetadata;
35
+ tokenSavings: {
36
+ traditional: number;
37
+ codedna: number;
38
+ saved: number;
39
+ percentSaved: string;
40
+ };
41
+ error?: undefined;
42
+ details?: undefined;
43
+ message?: undefined;
44
+ supported?: undefined;
45
+ }>;
46
+ /**
47
+ * Handle codedna_generate_frontend tool call
48
+ */
49
+ export declare function handleGenerateFrontend(args: any): Promise<{
50
+ error: string;
51
+ details: string[] | undefined;
52
+ message?: undefined;
53
+ frameworks?: undefined;
54
+ status?: undefined;
55
+ } | {
56
+ error: string;
57
+ message: string;
58
+ frameworks: string[];
59
+ status: string;
60
+ details?: undefined;
61
+ }>;
62
+ /**
63
+ * Handle codedna_generate_component tool call
64
+ */
65
+ export declare function handleGenerateComponent(args: any): Promise<{
66
+ error: string;
67
+ details: string[] | undefined;
68
+ message?: undefined;
69
+ types?: undefined;
70
+ frameworks?: undefined;
71
+ status?: undefined;
72
+ } | {
73
+ error: string;
74
+ message: string;
75
+ types: string[];
76
+ frameworks: string[];
77
+ status: string;
78
+ details?: undefined;
79
+ }>;
80
+ /**
81
+ * Handle codedna_list_generators tool call
82
+ */
83
+ export declare function handleListGenerators(): Promise<{
84
+ generators: import("../codedna/types.js").GeneratorMetadata[];
85
+ summary: {
86
+ total: number;
87
+ byFramework: any;
88
+ };
89
+ error?: undefined;
90
+ message?: undefined;
91
+ } | {
92
+ error: string;
93
+ message: string;
94
+ generators?: undefined;
95
+ summary?: undefined;
96
+ }>;
97
+ /**
98
+ * Handle codedna_validate_spec tool call
99
+ */
100
+ export declare function handleValidateSpec(args: any): Promise<{
101
+ valid: boolean;
102
+ entity: {
103
+ name: string;
104
+ fields: {
105
+ name: string;
106
+ type: import("../codedna/parser.js").FieldType;
107
+ constraints: ("default" | "min" | "max" | "required" | "unique" | "hashed" | "index")[];
108
+ }[];
109
+ };
110
+ summary: {
111
+ totalFields: number;
112
+ hasReferences: boolean;
113
+ hasEnums: boolean;
114
+ hasConstraints: boolean;
115
+ };
116
+ errors?: undefined;
117
+ } | {
118
+ valid: boolean;
119
+ errors: string[] | undefined;
120
+ entity?: undefined;
121
+ summary?: undefined;
122
+ }>;