kalo-cli 0.1.3 → 0.3.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/README.md +80 -46
- package/bin/kalo.ts +3 -2
- package/package.json +7 -3
- package/plopfile.ts +183 -26
- package/generators/ai-enhancer/index.ts +0 -283
- package/generators/ai-enhancer/keywords.json +0 -1158
- package/generators/constants.ts +0 -52
- package/generators/django-app/index.ts +0 -74
- package/generators/django-app/templates/admin.py.hbs +0 -6
- package/generators/django-app/templates/apps.py.hbs +0 -9
- package/generators/django-app/templates/init.py.hbs +0 -0
- package/generators/django-app/templates/models_init.py.hbs +0 -2
- package/generators/django-app/templates/urls.py.hbs +0 -8
- package/generators/django-app/templates/views.py.hbs +0 -5
- package/generators/django-channel/index.ts +0 -83
- package/generators/django-channel/templates/consumer.py.hbs +0 -47
- package/generators/django-channel/templates/routing.py.hbs +0 -8
- package/generators/django-form/index.ts +0 -67
- package/generators/django-form/templates/form.py.hbs +0 -12
- package/generators/django-form/templates/forms_file.py.hbs +0 -6
- package/generators/django-form/templates/model_form.py.hbs +0 -18
- package/generators/django-view/index.ts +0 -98
- package/generators/django-view/templates/view_cbv.py.hbs +0 -11
- package/generators/django-view/templates/view_fbv.py.hbs +0 -7
- package/generators/django-view/templates/view_template.html.hbs +0 -8
- package/generators/docs/index.ts +0 -36
- package/generators/help/index.ts +0 -84
- package/generators/main/index.ts +0 -421
- package/generators/utils/ai/common.ts +0 -141
- package/generators/utils/ai/index.ts +0 -2
- package/generators/utils/analysis.ts +0 -92
- package/generators/utils/code-manipulation.ts +0 -119
- package/generators/utils/config.ts +0 -43
- package/generators/utils/filesystem.ts +0 -146
- package/generators/utils/index.ts +0 -47
- package/generators/utils/plop-actions.ts +0 -104
- package/generators/utils/search.ts +0 -24
- package/generators/wagtail-admin/index.ts +0 -127
- package/generators/wagtail-admin/templates/admin_view.html.hbs +0 -21
- package/generators/wagtail-admin/templates/admin_view.py.hbs +0 -15
- package/generators/wagtail-admin/templates/component.html.hbs +0 -6
- package/generators/wagtail-admin/templates/component.py.hbs +0 -11
- package/generators/wagtail-admin/templates/wagtail_hooks.py.hbs +0 -18
- package/generators/wagtail-block/index.ts +0 -55
- package/generators/wagtail-block/templates/block_class.py.hbs +0 -13
- package/generators/wagtail-block/templates/block_template.html.hbs +0 -5
- package/generators/wagtail-page/actions/model.ts +0 -23
- package/generators/wagtail-page/actions/orderable.ts +0 -26
- package/generators/wagtail-page/actions/page.ts +0 -45
- package/generators/wagtail-page/actions/snippet.ts +0 -22
- package/generators/wagtail-page/index.ts +0 -63
- package/generators/wagtail-page/templates/django_model.py.hbs +0 -18
- package/generators/wagtail-page/templates/orderable_model.py.hbs +0 -21
- package/generators/wagtail-page/templates/page_pair_model.py.hbs +0 -62
- package/generators/wagtail-page/templates/page_template.html.hbs +0 -14
- package/generators/wagtail-page/templates/snippet_model.py.hbs +0 -24
- package/tsconfig.json +0 -29
package/README.md
CHANGED
|
@@ -1,68 +1,102 @@
|
|
|
1
|
-
# Kalo -
|
|
1
|
+
# Kalo CLI - Django/Wagtail Generator
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This is a command-line interface tool for generating Django and Wagtail components using Plop.js.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Features
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- Generate Django models, views, forms, APIs, and more
|
|
8
|
+
- Generate Wagtail pages, snippets, blocks, and other components
|
|
9
|
+
- Smart file appending to avoid duplicate classes
|
|
10
|
+
- Configuration loading from `kalo.config.json`
|
|
11
|
+
- Project root detection
|
|
12
|
+
- Searchable prompts for selecting existing apps
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
# Installation de Bun (Mac, Linux, WSL)
|
|
11
|
-
curl -fsSL https://bun.sh/install | bash
|
|
14
|
+
## Utilities
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
powershell -c "irm bun.sh/install.ps1 | iex"
|
|
15
|
-
```
|
|
16
|
+
The project includes several utility modules:
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
### Analysis Utilities (`src/utils/analysis.ts`)
|
|
19
|
+
- Functions to analyze Python files and extract class definitions
|
|
20
|
+
- Used to prevent duplicate class generation
|
|
18
21
|
|
|
19
|
-
###
|
|
22
|
+
### Code Manipulation Utilities (`src/utils/code-manipulation.ts`)
|
|
23
|
+
- Functions to inject markers in code for AI-assisted generation
|
|
24
|
+
- Apply generated code with imports, replacements, and code injection
|
|
20
25
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
bun add -g kalo-cli
|
|
25
|
-
```
|
|
26
|
+
### Configuration Utilities (`src/utils/config.ts`)
|
|
27
|
+
- Load configuration from `kalo.config.json` or `kalo.config.js`
|
|
28
|
+
- Support for AI providers, temperature settings, and verbosity levels
|
|
26
29
|
|
|
30
|
+
### File System Utilities (`src/utils/filesystem.ts`)
|
|
31
|
+
- Find project root directory
|
|
32
|
+
- Resolve application paths
|
|
33
|
+
- Get list of existing Django applications
|
|
34
|
+
- Get files and directories within an application
|
|
27
35
|
|
|
28
|
-
|
|
36
|
+
### Naming Conventions (`src/utils/naming-conventions.ts`)
|
|
37
|
+
- Validation functions for model names, app names, and page names
|
|
38
|
+
- Field validation utilities
|
|
29
39
|
|
|
30
|
-
###
|
|
40
|
+
### Plop Actions (`src/utils/plop-actions.ts`)
|
|
41
|
+
- `createAppendActions`: Creates pairs of actions to ensure files exist and append content
|
|
42
|
+
- Prevents duplicate class generation by checking existing classes
|
|
31
43
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
44
|
+
### Search Utilities (`src/utils/search.ts`)
|
|
45
|
+
- Fuzzy searching for autocomplete prompts
|
|
46
|
+
- Filter choices based on user input
|
|
35
47
|
|
|
36
|
-
###
|
|
48
|
+
### String Formatting (`src/utils/index.ts`)
|
|
49
|
+
- Format strings to slugs, snake_case, or PascalCase
|
|
37
50
|
|
|
38
|
-
|
|
51
|
+
## Usage
|
|
39
52
|
|
|
40
|
-
|
|
53
|
+
```bash
|
|
54
|
+
bun run plop # List all available generators
|
|
55
|
+
bun run plop model # Generate a new Django model
|
|
56
|
+
bun run plop view # Generate a new Django view
|
|
57
|
+
bun run plop wagtail-page # Generate a new Wagtail page
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Generators
|
|
61
|
+
|
|
62
|
+
The project includes generators for:
|
|
63
|
+
|
|
64
|
+
- Django apps
|
|
65
|
+
- Django models
|
|
66
|
+
- Django views
|
|
67
|
+
- Django forms
|
|
68
|
+
- Django APIs
|
|
69
|
+
- Django QuerySets
|
|
70
|
+
- Wagtail pages
|
|
71
|
+
- Wagtail snippets
|
|
72
|
+
- Wagtail blocks
|
|
73
|
+
- Wagtail streamfields
|
|
74
|
+
- Wagtail forms
|
|
75
|
+
- Wagtail tags
|
|
76
|
+
- Wagtail groups
|
|
77
|
+
- Wagtail hooks
|
|
78
|
+
- Wagtail locales
|
|
79
|
+
- Wagtail admin
|
|
80
|
+
- Wagtail commands
|
|
81
|
+
- Wagtail templates
|
|
82
|
+
- Wagtail API v2
|
|
83
|
+
|
|
84
|
+
## Configuration
|
|
85
|
+
|
|
86
|
+
Create a `kalo.config.json` file in your project root:
|
|
41
87
|
|
|
42
88
|
```json
|
|
43
89
|
{
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
90
|
+
"aiProvider": "openai",
|
|
91
|
+
"temperature": 0.7,
|
|
92
|
+
"verbosity": "standard",
|
|
93
|
+
"appDir": "src"
|
|
48
94
|
}
|
|
49
95
|
```
|
|
50
96
|
|
|
51
|
-
|
|
52
|
-
- `appDir` : Le dossier où se trouvent vos applications Django (par défaut: `app`).
|
|
53
|
-
- `aiProvider` : Le fournisseur d'IA par défaut (`Qwen`, `Ollama`, `Mistral`, `OpenAI`).
|
|
54
|
-
- `temperature` : La créativité de l'IA (entre 0 et 1).
|
|
55
|
-
- `verbosity` : Le niveau de détail des commentaires générés (`minimal`, `standard`, `verbose`).
|
|
56
|
-
|
|
57
|
-
### Si installé dans un projet
|
|
58
|
-
|
|
59
|
-
Utilisez `bun run` ou `bunx` :
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
bun run kalo
|
|
63
|
-
# ou
|
|
64
|
-
bunx kalo
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
Si vous utilisez `npx`, assurez-vous que l'environnement peut exécuter le script (nécessite un environnement compatible avec le shebang `#!/usr/bin/env bun`, généralement Mac/Linux/WSL). Sur Windows standard, préférez `bunx`.
|
|
97
|
+
## Integration Notes
|
|
68
98
|
|
|
99
|
+
- The `createAppendActions` utility is now used in ALL generators (model, view, api, form, queryset, wagtail-admin, wagtail-api-v2, wagtail-block, wagtail-command, wagtail-form, wagtail-hook, wagtail-page, wagtail-snippet, wagtail-streamfield, wagtail-tag, wagtail-template) to append to existing files instead of overwriting them
|
|
100
|
+
- The `plopfile.ts` now loads configuration and sets up project root detection
|
|
101
|
+
- Multiple generators now use searchable prompts for app selection (api, form, queryset, wagtail-admin, wagtail-api-v2, wagtail-block, wagtail-command, wagtail-form, wagtail-hook, wagtail-snippet, wagtail-streamfield, wagtail-tag, wagtail-template)
|
|
102
|
+
- File system utilities are used throughout for path resolution and app detection
|
package/bin/kalo.ts
CHANGED
|
@@ -4,8 +4,9 @@ import fs from "node:fs";
|
|
|
4
4
|
import minimist from "minimist";
|
|
5
5
|
import { Plop, run } from "plop";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { findProjectRoot } from "../src/utils/filesystem";
|
|
8
|
+
import { loadConfig } from "../src/utils/config";
|
|
9
|
+
|
|
9
10
|
|
|
10
11
|
const args = process.argv.slice(2);
|
|
11
12
|
const argv = minimist(args);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kalo-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Générateur de code structuré et uniforme pour Django et Wagtail",
|
|
5
5
|
"bin": {
|
|
6
6
|
"kalo": "./bin/kalo.ts"
|
|
@@ -8,7 +8,10 @@
|
|
|
8
8
|
"type": "module",
|
|
9
9
|
"scripts": {
|
|
10
10
|
"kalo": "bun bin/kalo.ts",
|
|
11
|
-
"
|
|
11
|
+
"dev": "plop --plopfile plopfile.ts",
|
|
12
|
+
"doc": "serve dist/docs -p 3333",
|
|
13
|
+
"doc:dev": "bun run --port 3333 docs-site/index.html",
|
|
14
|
+
"build": "bun scripts/build-docs.ts"
|
|
12
15
|
},
|
|
13
16
|
"keywords": [
|
|
14
17
|
"django",
|
|
@@ -23,7 +26,8 @@
|
|
|
23
26
|
"generators",
|
|
24
27
|
"utils",
|
|
25
28
|
"plopfile.ts",
|
|
26
|
-
"tsconfig.json"
|
|
29
|
+
"tsconfig.json",
|
|
30
|
+
"dist"
|
|
27
31
|
],
|
|
28
32
|
"devDependencies": {
|
|
29
33
|
"@types/bun": "latest"
|
package/plopfile.ts
CHANGED
|
@@ -1,26 +1,183 @@
|
|
|
1
|
-
import { NodePlopAPI } from 'plop';
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
1
|
+
import { NodePlopAPI } from 'plop';
|
|
2
|
+
import { findProjectRoot } from './src/utils/filesystem';
|
|
3
|
+
import { loadConfig } from './src/utils/config';
|
|
4
|
+
|
|
5
|
+
// Import all generators
|
|
6
|
+
import appGenerator from './src/gen/app/index';
|
|
7
|
+
import modelGenerator from './src/gen/model/index';
|
|
8
|
+
import viewGenerator from './src/gen/view/index';
|
|
9
|
+
import formGenerator from './src/gen/form/index';
|
|
10
|
+
import apiGenerator from './src/gen/api/index';
|
|
11
|
+
import querysetGenerator from './src/gen/queryset/index';
|
|
12
|
+
import wagtailPageGenerator from './src/gen/wagtail-page/index';
|
|
13
|
+
import wagtailSnippetGenerator from './src/gen/wagtail-snippet/index';
|
|
14
|
+
import wagtailBlockGenerator from './src/gen/wagtail-block/index';
|
|
15
|
+
import wagtailStreamfieldGenerator from './src/gen/wagtail-streamfield/index';
|
|
16
|
+
import wagtailFormGenerator from './src/gen/wagtail-form/index';
|
|
17
|
+
import wagtailTagGenerator from './src/gen/wagtail-tag/index';
|
|
18
|
+
import wagtailGroupGenerator from './src/gen/wagtail-group/index';
|
|
19
|
+
import wagtailHookGenerator from './src/gen/wagtail-hook/index';
|
|
20
|
+
import wagtailLocaleGenerator from './src/gen/wagtail-locale/index';
|
|
21
|
+
import wagtailAdminGenerator from './src/gen/wagtail-admin/index';
|
|
22
|
+
import wagtailCommandGenerator from './src/gen/wagtail-command/index';
|
|
23
|
+
import wagtailTemplateGenerator from './src/gen/wagtail-template/index';
|
|
24
|
+
import wagtailApiV2Generator from './src/gen/wagtail-api-v2/index';
|
|
25
|
+
|
|
26
|
+
export default async function (plop: NodePlopAPI) {
|
|
27
|
+
// Load configuration
|
|
28
|
+
const config = await loadConfig();
|
|
29
|
+
|
|
30
|
+
// Register custom helpers
|
|
31
|
+
plop.setHelper('toPascalCase', function(str: string) {
|
|
32
|
+
if (!str) return str;
|
|
33
|
+
return str.replace(/(\w)(\w*)/g, (g0, g1, g2) => g1.toUpperCase() + g2.toLowerCase()).replace(/\W/g, '');
|
|
34
|
+
});
|
|
35
|
+
// Snake case helper to enforce snake_case filenames
|
|
36
|
+
plop.setHelper('toSnakeCase', function(str: string) {
|
|
37
|
+
if (!str) return str;
|
|
38
|
+
return String(str)
|
|
39
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
40
|
+
.replace(/[\s\-]+/g, '_')
|
|
41
|
+
.toLowerCase();
|
|
42
|
+
});
|
|
43
|
+
// Title case for human-facing labels
|
|
44
|
+
plop.setHelper('titleCase', function(str: string) {
|
|
45
|
+
if (!str) return str;
|
|
46
|
+
return String(str)
|
|
47
|
+
.replace(/[_\-]+/g, ' ')
|
|
48
|
+
.replace(/\s+/g, ' ')
|
|
49
|
+
.trim()
|
|
50
|
+
.split(' ')
|
|
51
|
+
.map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
|
|
52
|
+
.join(' ');
|
|
53
|
+
});
|
|
54
|
+
// Map generic type to Django Model Field
|
|
55
|
+
plop.setHelper('djangoModelField', function(type: string) {
|
|
56
|
+
if (!type) return 'CharField';
|
|
57
|
+
const t = String(type).toLowerCase();
|
|
58
|
+
const map: Record<string, string> = {
|
|
59
|
+
char: 'CharField',
|
|
60
|
+
text: 'TextField',
|
|
61
|
+
integer: 'IntegerField',
|
|
62
|
+
small_integer: 'SmallIntegerField',
|
|
63
|
+
positive_integer: 'PositiveIntegerField',
|
|
64
|
+
float: 'FloatField',
|
|
65
|
+
decimal: 'DecimalField',
|
|
66
|
+
boolean: 'BooleanField',
|
|
67
|
+
date: 'DateField',
|
|
68
|
+
datetime: 'DateTimeField',
|
|
69
|
+
email: 'EmailField',
|
|
70
|
+
url: 'URLField',
|
|
71
|
+
file: 'FileField',
|
|
72
|
+
image: 'ImageField',
|
|
73
|
+
uuid: 'UUIDField',
|
|
74
|
+
json: 'JSONField',
|
|
75
|
+
ip_address: 'GenericIPAddressField',
|
|
76
|
+
foreign_key: 'ForeignKey',
|
|
77
|
+
many_to_many: 'ManyToManyField',
|
|
78
|
+
one_to_one: 'OneToOneField',
|
|
79
|
+
};
|
|
80
|
+
return map[t] || (type.endsWith('Field') ? type : 'CharField');
|
|
81
|
+
});
|
|
82
|
+
// Map generic type to Django Form Field
|
|
83
|
+
plop.setHelper('djangoFormField', function(type: string) {
|
|
84
|
+
if (!type) return 'CharField';
|
|
85
|
+
const t = String(type).toLowerCase();
|
|
86
|
+
const map: Record<string, string> = {
|
|
87
|
+
char: 'CharField',
|
|
88
|
+
text: 'CharField',
|
|
89
|
+
integer: 'IntegerField',
|
|
90
|
+
float: 'FloatField',
|
|
91
|
+
decimal: 'DecimalField',
|
|
92
|
+
boolean: 'BooleanField',
|
|
93
|
+
date: 'DateField',
|
|
94
|
+
datetime: 'DateTimeField',
|
|
95
|
+
email: 'EmailField',
|
|
96
|
+
url: 'URLField',
|
|
97
|
+
file: 'FileField',
|
|
98
|
+
image: 'ImageField',
|
|
99
|
+
uuid: 'UUIDField',
|
|
100
|
+
json: 'JSONField',
|
|
101
|
+
};
|
|
102
|
+
return map[t] || (type.endsWith('Field') ? type : 'CharField');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
plop.setHelper('trim', (str) => {
|
|
106
|
+
return str ? String(str).trim() : '';
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
plop.setHelper('eq', (a, b) => {
|
|
110
|
+
return a == b;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
plop.setHelper('split', (str, separator) => {
|
|
114
|
+
return str ? String(str).split(separator) : [];
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
plop.setHelper('replace', (str, find, replace) => {
|
|
118
|
+
return str ? String(str).split(find).join(replace) : '';
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
plop.setHelper('includes', (arr, value) => {
|
|
122
|
+
if (Array.isArray(arr)) return arr.includes(value);
|
|
123
|
+
if (typeof arr === 'string') return arr.includes(value);
|
|
124
|
+
return false;
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Set the project root as a global helper
|
|
128
|
+
plop.setHelper('projectRoot', function() {
|
|
129
|
+
return findProjectRoot();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Set configuration as a global helper
|
|
133
|
+
plop.setHelper('getConfig', function(key: string) {
|
|
134
|
+
return config[key] || null;
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Register the autocomplete prompt
|
|
138
|
+
try {
|
|
139
|
+
const autoCompletePrompt = await import('inquirer-autocomplete-prompt');
|
|
140
|
+
plop.setPrompt('autocomplete', autoCompletePrompt.default);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.warn('inquirer-autocomplete-prompt not found. Install it to enable autocomplete prompts.');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Register all generators
|
|
146
|
+
appGenerator(plop);
|
|
147
|
+
modelGenerator(plop);
|
|
148
|
+
viewGenerator(plop);
|
|
149
|
+
formGenerator(plop);
|
|
150
|
+
apiGenerator(plop);
|
|
151
|
+
querysetGenerator(plop);
|
|
152
|
+
wagtailPageGenerator(plop);
|
|
153
|
+
wagtailSnippetGenerator(plop);
|
|
154
|
+
wagtailBlockGenerator(plop);
|
|
155
|
+
wagtailStreamfieldGenerator(plop);
|
|
156
|
+
wagtailFormGenerator(plop);
|
|
157
|
+
wagtailTagGenerator(plop);
|
|
158
|
+
wagtailGroupGenerator(plop);
|
|
159
|
+
wagtailHookGenerator(plop);
|
|
160
|
+
wagtailLocaleGenerator(plop);
|
|
161
|
+
wagtailAdminGenerator(plop);
|
|
162
|
+
wagtailCommandGenerator(plop);
|
|
163
|
+
wagtailTemplateGenerator(plop);
|
|
164
|
+
wagtailApiV2Generator(plop);
|
|
165
|
+
|
|
166
|
+
plop.setGenerator('component', {
|
|
167
|
+
description: 'Create a new component',
|
|
168
|
+
prompts: [
|
|
169
|
+
{
|
|
170
|
+
type: 'input',
|
|
171
|
+
name: 'name',
|
|
172
|
+
message: 'Component name:'
|
|
173
|
+
}
|
|
174
|
+
],
|
|
175
|
+
actions: [
|
|
176
|
+
{
|
|
177
|
+
type: 'add',
|
|
178
|
+
path: 'src/components/{{name}}.tsx',
|
|
179
|
+
template: 'export const {{name}} = () => <div>{{name}}</div>;'
|
|
180
|
+
}
|
|
181
|
+
]
|
|
182
|
+
});
|
|
183
|
+
};
|
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import { PlopGeneratorConfig } from 'plop';
|
|
3
|
-
import { generateWithModel } from '../utils/ai/common';
|
|
4
|
-
import { injectMarkers, applyGeneratedCode } from '../utils/code-manipulation';
|
|
5
|
-
import { Glob } from "bun";
|
|
6
|
-
import { searchChoices } from '../utils/search';
|
|
7
|
-
import { findProjectRoot } from '../utils/filesystem';
|
|
8
|
-
import { AI_PROVIDER_TYPES, AI_TEMP_TYPES, BACK_VALUE, EXIT_VALUE } from '../constants';
|
|
9
|
-
import keywordConfig from './keywords.json';
|
|
10
|
-
|
|
11
|
-
const getHelpItems = (input: string | undefined): any[] => {
|
|
12
|
-
const items: any[] = [];
|
|
13
|
-
|
|
14
|
-
if (input) {
|
|
15
|
-
const inputLower = input.toLowerCase();
|
|
16
|
-
let lastMatchIndex = -1;
|
|
17
|
-
let matchedInstruction = null;
|
|
18
|
-
|
|
19
|
-
const keywords = Array.isArray(keywordConfig) ? keywordConfig : (keywordConfig as any).keywords || [];
|
|
20
|
-
|
|
21
|
-
for (const entry of keywords) {
|
|
22
|
-
if (entry.terms && Array.isArray(entry.terms)) {
|
|
23
|
-
for (const term of entry.terms) {
|
|
24
|
-
const index = inputLower.lastIndexOf(term.toLowerCase());
|
|
25
|
-
if (index !== -1 && index >= lastMatchIndex) {
|
|
26
|
-
lastMatchIndex = index;
|
|
27
|
-
matchedInstruction = entry.instruction;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (matchedInstruction) {
|
|
34
|
-
// Split instruction into lines of max 80 chars for better visibility
|
|
35
|
-
const words = matchedInstruction.split(' ');
|
|
36
|
-
let currentLine = '💡 ';
|
|
37
|
-
|
|
38
|
-
for (const word of words) {
|
|
39
|
-
if ((currentLine + word).length > 80) {
|
|
40
|
-
items.push({ name: currentLine, value: 'INFO', disabled: true });
|
|
41
|
-
currentLine = ' ' + word + ' ';
|
|
42
|
-
} else {
|
|
43
|
-
currentLine += word + ' ';
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
if (currentLine.trim()) {
|
|
47
|
-
items.push({ name: currentLine, value: 'INFO', disabled: true });
|
|
48
|
-
}
|
|
49
|
-
items.push({ name: '────────────────────────────────────────', value: 'SEP', disabled: true });
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return items;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
export const helpGenerator: PlopGeneratorConfig = {
|
|
56
|
-
description: 'Search for AI instructions and keywords',
|
|
57
|
-
prompts: [
|
|
58
|
-
{
|
|
59
|
-
type: 'autocomplete',
|
|
60
|
-
name: 'query',
|
|
61
|
-
message: 'Type a keyword to see associated instructions (e.g. "model", "field"):',
|
|
62
|
-
suggestOnly: true,
|
|
63
|
-
source: async (answers, input) => {
|
|
64
|
-
const choices: any[] = [];
|
|
65
|
-
|
|
66
|
-
const helpItems = getHelpItems(input);
|
|
67
|
-
if (helpItems.length > 0) {
|
|
68
|
-
choices.push(...helpItems);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (!input) {
|
|
72
|
-
choices.push({ name: 'Type to search...', value: 'INFO', disabled: true });
|
|
73
|
-
choices.push({ name: 'Exit', value: EXIT_VALUE });
|
|
74
|
-
return choices;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
choices.push({ name: 'Exit', value: EXIT_VALUE });
|
|
78
|
-
return choices;
|
|
79
|
-
},
|
|
80
|
-
validate: (value) => {
|
|
81
|
-
if (value === EXIT_VALUE || value?.toLowerCase() === 'exit') {
|
|
82
|
-
return true;
|
|
83
|
-
}
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
],
|
|
88
|
-
actions: []
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
const handleInput = (value: any) => {
|
|
92
|
-
if (value === EXIT_VALUE || (typeof value === 'string' && value.toLowerCase() === 'exit')) {
|
|
93
|
-
return EXIT_VALUE;
|
|
94
|
-
}
|
|
95
|
-
return value;
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const isBack = (answers: any) => {
|
|
99
|
-
return Object.values(answers).includes(BACK_VALUE) || Object.values(answers).includes(EXIT_VALUE);
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
export const aiEnhancerGenerator: PlopGeneratorConfig = {
|
|
103
|
-
description: 'Enhance existing models with AI generated code',
|
|
104
|
-
prompts: [
|
|
105
|
-
{
|
|
106
|
-
type: 'autocomplete',
|
|
107
|
-
name: 'filePath',
|
|
108
|
-
message: 'Select the model file to enhance:',
|
|
109
|
-
source: async (answers, input) => {
|
|
110
|
-
const glob = new Glob("**/*.py");
|
|
111
|
-
const files: string[] = [];
|
|
112
|
-
const rootDir = findProjectRoot();
|
|
113
|
-
// Scans the project root
|
|
114
|
-
for await (const file of glob.scan(rootDir)) {
|
|
115
|
-
// Filter for likely python model files or snippets
|
|
116
|
-
if (file.endsWith('.py') && (file.includes("models") || file.includes("snippet"))) {
|
|
117
|
-
files.push(file);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
const choices = files.map(f => ({ name: f, value: f }));
|
|
121
|
-
const results = await searchChoices(input, choices);
|
|
122
|
-
results.push({ name: 'Exit', value: EXIT_VALUE });
|
|
123
|
-
return results;
|
|
124
|
-
},
|
|
125
|
-
filter: handleInput
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
type: 'autocomplete',
|
|
129
|
-
name: 'instruction',
|
|
130
|
-
message: 'What modification/addition do you want to generate?',
|
|
131
|
-
when: (answers) => !isBack(answers),
|
|
132
|
-
suggestOnly: true,
|
|
133
|
-
source: async (answers, input) => {
|
|
134
|
-
// We provide a "Exit" option even for input by using autocomplete with suggestOnly
|
|
135
|
-
if (!input) return [{ name: 'Exit', value: EXIT_VALUE }];
|
|
136
|
-
return [{ name: 'Exit', value: EXIT_VALUE }];
|
|
137
|
-
},
|
|
138
|
-
validate: (value) => {
|
|
139
|
-
if (value === EXIT_VALUE || value?.toLowerCase() === 'exit') return true;
|
|
140
|
-
return value ? true : 'Instruction is required';
|
|
141
|
-
},
|
|
142
|
-
filter: handleInput
|
|
143
|
-
},
|
|
144
|
-
{
|
|
145
|
-
type: 'autocomplete',
|
|
146
|
-
name: 'aiProvider',
|
|
147
|
-
message: 'Select AI Provider:',
|
|
148
|
-
default: 'Qwen',
|
|
149
|
-
when: (answers: any) => !isBack(answers) && !answers.config?.aiProvider,
|
|
150
|
-
source: async (answers, input) => {
|
|
151
|
-
const choices = AI_PROVIDER_TYPES.map(p => ({ name: p, value: p }));
|
|
152
|
-
const results = await searchChoices(input, choices);
|
|
153
|
-
results.push({ name: 'Exit', value: EXIT_VALUE });
|
|
154
|
-
return results;
|
|
155
|
-
},
|
|
156
|
-
filter: handleInput
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
type: 'autocomplete',
|
|
160
|
-
name: 'temperature',
|
|
161
|
-
message: 'Select Creativity Level:',
|
|
162
|
-
default: 0.5,
|
|
163
|
-
when: (answers: any) => !isBack(answers) && answers.config?.temperature === undefined,
|
|
164
|
-
source: async (answers, input) => {
|
|
165
|
-
const results = await searchChoices(input, AI_TEMP_TYPES);
|
|
166
|
-
results.push({ name: 'Exit', value: EXIT_VALUE });
|
|
167
|
-
return results;
|
|
168
|
-
},
|
|
169
|
-
filter: handleInput
|
|
170
|
-
},
|
|
171
|
-
{
|
|
172
|
-
type: 'autocomplete',
|
|
173
|
-
name: 'verbosity',
|
|
174
|
-
message: 'Select Verbosity Level:',
|
|
175
|
-
default: 'standard',
|
|
176
|
-
when: (answers: any) => !isBack(answers) && !answers.config?.verbosity,
|
|
177
|
-
source: async (answers, input) => {
|
|
178
|
-
const choices = [
|
|
179
|
-
{ name: 'Minimal (Code only)', value: 'minimal' },
|
|
180
|
-
{ name: 'Standard (Code + basic comments)', value: 'standard' },
|
|
181
|
-
{ name: 'Verbose (Detailed docs)', value: 'verbose' }
|
|
182
|
-
];
|
|
183
|
-
const results = await searchChoices(input, choices);
|
|
184
|
-
results.push({ name: 'Exit', value: EXIT_VALUE });
|
|
185
|
-
return results;
|
|
186
|
-
},
|
|
187
|
-
filter: handleInput
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
type: 'input',
|
|
191
|
-
name: 'constraints',
|
|
192
|
-
message: 'Terms to listen to / Constraints (comma separated, optional):',
|
|
193
|
-
when: (answers) => !isBack(answers),
|
|
194
|
-
filter: (value) => {
|
|
195
|
-
if (value === EXIT_VALUE || (typeof value === 'string' && value.toLowerCase() === 'exit')) {
|
|
196
|
-
return EXIT_VALUE;
|
|
197
|
-
}
|
|
198
|
-
return value.split(',').map((s: string) => s.trim()).filter((s: string) => s.length > 0);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
],
|
|
202
|
-
actions: (data) => {
|
|
203
|
-
// Handle "Back" action (Abort generator)
|
|
204
|
-
if (Object.values(data).includes(BACK_VALUE) || Object.values(data).includes(EXIT_VALUE)) {
|
|
205
|
-
return [];
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return [
|
|
209
|
-
{
|
|
210
|
-
type: 'modify',
|
|
211
|
-
path: '{{filePath}}',
|
|
212
|
-
transform: async (fileContent, data) => {
|
|
213
|
-
const className = data.targetClass || 'AI_GENERATED';
|
|
214
|
-
|
|
215
|
-
// 1. Inject Markers if missing
|
|
216
|
-
const contentWithMarkers = injectMarkers(fileContent, className);
|
|
217
|
-
|
|
218
|
-
// Keyword analysis for enhanced context
|
|
219
|
-
const keywordInstructions: string[] = [];
|
|
220
|
-
const instructionLower = data.instruction.toLowerCase();
|
|
221
|
-
|
|
222
|
-
const keywords = Array.isArray(keywordConfig) ? keywordConfig : (keywordConfig as any).keywords || [];
|
|
223
|
-
for (const keyword of keywords) {
|
|
224
|
-
if (keyword.term && instructionLower.includes(keyword.term)) {
|
|
225
|
-
keywordInstructions.push(keyword.instruction);
|
|
226
|
-
} else if (keyword.terms) {
|
|
227
|
-
if (keyword.terms.some((t: string) => instructionLower.includes(t))) {
|
|
228
|
-
keywordInstructions.push(keyword.instruction);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Build options object
|
|
234
|
-
const options = {
|
|
235
|
-
temperature: data.temperature,
|
|
236
|
-
constraints: [
|
|
237
|
-
...(data.constraints || []),
|
|
238
|
-
`Target Class: ${className}`,
|
|
239
|
-
"Output Format: JSON with keys: 'imports' (string[]), 'replacements' ({search, replace}[]), 'code' (string).",
|
|
240
|
-
"Rules:",
|
|
241
|
-
"1. 'imports': New import statements.",
|
|
242
|
-
"2. 'replacements': exact 'search' string to find and 'replace' string to overwrite. Use for modifying existing lists/dicts.",
|
|
243
|
-
"3. 'code': New methods/logic to be injected into the class.",
|
|
244
|
-
...keywordInstructions
|
|
245
|
-
],
|
|
246
|
-
verbosity: data.verbosity,
|
|
247
|
-
plopContext: data,
|
|
248
|
-
responseFormat: 'json'
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
let generatedResponseString = '';
|
|
252
|
-
|
|
253
|
-
try {
|
|
254
|
-
const providerName = data.aiProvider || 'Qwen';
|
|
255
|
-
const modelCommand = providerName.toLowerCase();
|
|
256
|
-
|
|
257
|
-
generatedResponseString = await generateWithModel(
|
|
258
|
-
contentWithMarkers, // context
|
|
259
|
-
data.instruction, // instruction
|
|
260
|
-
modelCommand,
|
|
261
|
-
providerName,
|
|
262
|
-
options as any
|
|
263
|
-
);
|
|
264
|
-
} catch (error) {
|
|
265
|
-
console.error('AI Generation Failed:', error);
|
|
266
|
-
return fileContent;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
let generatedData;
|
|
270
|
-
try {
|
|
271
|
-
generatedData = JSON.parse(generatedResponseString);
|
|
272
|
-
} catch (e) {
|
|
273
|
-
console.error('Failed to parse AI response as JSON.');
|
|
274
|
-
return contentWithMarkers;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// 2. Apply Changes
|
|
278
|
-
return applyGeneratedCode(contentWithMarkers, generatedData, className);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
]
|
|
282
|
-
}
|
|
283
|
-
};
|