kodu 1.2.0 → 2.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/AGENTS.md +36 -68
- package/README.md +97 -96
- package/dist/package.json +1 -2
- package/dist/src/app.module.js +0 -8
- package/dist/src/app.module.js.map +1 -1
- package/dist/src/commands/init/init.command.d.ts +2 -9
- package/dist/src/commands/init/init.command.js +15 -241
- package/dist/src/commands/init/init.command.js.map +1 -1
- package/dist/src/commands/pack/pack.command.d.ts +7 -0
- package/dist/src/commands/pack/pack.command.js +59 -3
- package/dist/src/commands/pack/pack.command.js.map +1 -1
- package/dist/src/core/config/config.schema.d.ts +0 -46
- package/dist/src/core/config/config.schema.js +1 -51
- package/dist/src/core/config/config.schema.js.map +1 -1
- package/dist/src/core/config/config.service.js +2 -2
- package/dist/src/core/config/config.service.js.map +1 -1
- package/dist/src/core/config/prompt.service.d.ts +1 -4
- package/dist/src/core/config/prompt.service.js +4 -17
- package/dist/src/core/config/prompt.service.js.map +1 -1
- package/dist/src/shared/constants.d.ts +0 -4
- package/dist/src/shared/constants.js +1 -5
- package/dist/src/shared/constants.js.map +1 -1
- package/dist/src/shared/git/git.module.js +0 -2
- package/dist/src/shared/git/git.module.js.map +1 -1
- package/dist/src/shared/git/git.service.d.ts +0 -8
- package/dist/src/shared/git/git.service.js +2 -34
- package/dist/src/shared/git/git.service.js.map +1 -1
- package/dist/src/shared/tokenizer/tokenizer.module.js +0 -2
- package/dist/src/shared/tokenizer/tokenizer.module.js.map +1 -1
- package/dist/src/shared/tokenizer/tokenizer.service.d.ts +0 -6
- package/dist/src/shared/tokenizer/tokenizer.service.js +8 -38
- package/dist/src/shared/tokenizer/tokenizer.service.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/kodu.schema.json +0 -139
- package/package.json +1 -2
- package/src/app.module.ts +0 -8
- package/src/commands/init/init.command.ts +15 -310
- package/src/commands/pack/pack.command.ts +56 -3
- package/src/core/config/config.schema.ts +1 -68
- package/src/core/config/config.service.ts +2 -2
- package/src/core/config/prompt.service.ts +4 -26
- package/src/shared/constants.ts +0 -4
- package/src/shared/git/git.module.ts +0 -2
- package/src/shared/git/git.service.ts +1 -33
- package/src/shared/tokenizer/tokenizer.module.ts +0 -2
- package/src/shared/tokenizer/tokenizer.service.ts +9 -39
- package/.kodu/prompts/.keep +0 -0
- package/.kodu/prompts/commit.md +0 -9
- package/.kodu/prompts/pack.md +0 -7
- package/.kodu/prompts/review-bug.md +0 -6
- package/.kodu/prompts/review-security.md +0 -6
- package/.kodu/prompts/review-style.md +0 -6
- package/.opencode/command/openspec-apply.md +0 -24
- package/.opencode/command/openspec-archive.md +0 -27
- package/.opencode/command/openspec-proposal.md +0 -29
- package/.opencode/skills/kodu-ops/SKILL.md +0 -184
- package/dist/src/commands/commit/commit.command.d.ts +0 -18
- package/dist/src/commands/commit/commit.command.js +0 -149
- package/dist/src/commands/commit/commit.command.js.map +0 -1
- package/dist/src/commands/commit/commit.module.d.ts +0 -2
- package/dist/src/commands/commit/commit.module.js +0 -25
- package/dist/src/commands/commit/commit.module.js.map +0 -1
- package/dist/src/commands/ops/ops.command.d.ts +0 -4
- package/dist/src/commands/ops/ops.command.js +0 -39
- package/dist/src/commands/ops/ops.command.js.map +0 -1
- package/dist/src/commands/ops/ops.module.d.ts +0 -2
- package/dist/src/commands/ops/ops.module.js +0 -33
- package/dist/src/commands/ops/ops.module.js.map +0 -1
- package/dist/src/commands/ops/ops.types.d.ts +0 -13
- package/dist/src/commands/ops/ops.types.js +0 -12
- package/dist/src/commands/ops/ops.types.js.map +0 -1
- package/dist/src/commands/ops/ops.utils.d.ts +0 -13
- package/dist/src/commands/ops/ops.utils.js +0 -121
- package/dist/src/commands/ops/ops.utils.js.map +0 -1
- package/dist/src/commands/ops/subcommands/ops-env.command.d.ts +0 -24
- package/dist/src/commands/ops/subcommands/ops-env.command.js +0 -156
- package/dist/src/commands/ops/subcommands/ops-env.command.js.map +0 -1
- package/dist/src/commands/ops/subcommands/ops-routes.command.d.ts +0 -22
- package/dist/src/commands/ops/subcommands/ops-routes.command.js +0 -203
- package/dist/src/commands/ops/subcommands/ops-routes.command.js.map +0 -1
- package/dist/src/commands/ops/subcommands/ops-service.command.d.ts +0 -22
- package/dist/src/commands/ops/subcommands/ops-service.command.js +0 -169
- package/dist/src/commands/ops/subcommands/ops-service.command.js.map +0 -1
- package/dist/src/commands/ops/subcommands/ops-sysinfo.command.d.ts +0 -14
- package/dist/src/commands/ops/subcommands/ops-sysinfo.command.js +0 -75
- package/dist/src/commands/ops/subcommands/ops-sysinfo.command.js.map +0 -1
- package/dist/src/commands/review/review.command.d.ts +0 -26
- package/dist/src/commands/review/review.command.js +0 -205
- package/dist/src/commands/review/review.command.js.map +0 -1
- package/dist/src/commands/review/review.module.d.ts +0 -2
- package/dist/src/commands/review/review.module.js +0 -26
- package/dist/src/commands/review/review.module.js.map +0 -1
- package/dist/src/core/config/default-prompts.d.ts +0 -9
- package/dist/src/core/config/default-prompts.js +0 -49
- package/dist/src/core/config/default-prompts.js.map +0 -1
- package/dist/src/shared/ai/ai.module.d.ts +0 -2
- package/dist/src/shared/ai/ai.module.js +0 -23
- package/dist/src/shared/ai/ai.module.js.map +0 -1
- package/dist/src/shared/ai/ai.service.d.ts +0 -22
- package/dist/src/shared/ai/ai.service.js +0 -164
- package/dist/src/shared/ai/ai.service.js.map +0 -1
- package/dist/src/shared/ssh/ssh.module.d.ts +0 -2
- package/dist/src/shared/ssh/ssh.module.js +0 -21
- package/dist/src/shared/ssh/ssh.module.js.map +0 -1
- package/dist/src/shared/ssh/ssh.service.d.ts +0 -11
- package/dist/src/shared/ssh/ssh.service.js +0 -53
- package/dist/src/shared/ssh/ssh.service.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/src/commands/commit/commit.command.ts +0 -139
- package/src/commands/commit/commit.module.ts +0 -12
- package/src/commands/ops/ops.command.ts +0 -30
- package/src/commands/ops/ops.module.ts +0 -20
- package/src/commands/ops/ops.types.ts +0 -24
- package/src/commands/ops/ops.utils.ts +0 -160
- package/src/commands/ops/subcommands/ops-env.command.ts +0 -165
- package/src/commands/ops/subcommands/ops-routes.command.ts +0 -221
- package/src/commands/ops/subcommands/ops-service.command.ts +0 -190
- package/src/commands/ops/subcommands/ops-sysinfo.command.ts +0 -77
- package/src/commands/review/review.command.ts +0 -199
- package/src/commands/review/review.module.ts +0 -13
- package/src/core/config/default-prompts.ts +0 -53
- package/src/shared/ai/ai.module.ts +0 -10
- package/src/shared/ai/ai.service.ts +0 -216
- package/src/shared/ssh/ssh.module.ts +0 -8
- package/src/shared/ssh/ssh.service.ts +0 -61
package/kodu.schema.json
CHANGED
|
@@ -5,74 +5,6 @@
|
|
|
5
5
|
"$schema": {
|
|
6
6
|
"type": "string"
|
|
7
7
|
},
|
|
8
|
-
"llm": {
|
|
9
|
-
"type": "object",
|
|
10
|
-
"properties": {
|
|
11
|
-
"model": {
|
|
12
|
-
"default": "openai/gpt-4o",
|
|
13
|
-
"type": "string",
|
|
14
|
-
"pattern": "^[a-zA-Z0-9-_]+\\/[a-zA-Z0-9-_.]+$"
|
|
15
|
-
},
|
|
16
|
-
"apiKeyEnv": {
|
|
17
|
-
"default": "OPENAI_API_KEY",
|
|
18
|
-
"type": "string"
|
|
19
|
-
},
|
|
20
|
-
"commands": {
|
|
21
|
-
"default": {
|
|
22
|
-
"commit": {
|
|
23
|
-
"modelSettings": {
|
|
24
|
-
"maxOutputTokens": 1500
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
"review": {
|
|
28
|
-
"modelSettings": {
|
|
29
|
-
"maxOutputTokens": 5000
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
"type": "object",
|
|
34
|
-
"properties": {
|
|
35
|
-
"commit": {
|
|
36
|
-
"type": "object",
|
|
37
|
-
"properties": {
|
|
38
|
-
"modelSettings": {
|
|
39
|
-
"type": "object",
|
|
40
|
-
"properties": {
|
|
41
|
-
"maxOutputTokens": {
|
|
42
|
-
"type": "integer",
|
|
43
|
-
"exclusiveMinimum": 0,
|
|
44
|
-
"maximum": 9007199254740991
|
|
45
|
-
}
|
|
46
|
-
},
|
|
47
|
-
"additionalProperties": {}
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
"additionalProperties": false
|
|
51
|
-
},
|
|
52
|
-
"review": {
|
|
53
|
-
"type": "object",
|
|
54
|
-
"properties": {
|
|
55
|
-
"modelSettings": {
|
|
56
|
-
"type": "object",
|
|
57
|
-
"properties": {
|
|
58
|
-
"maxOutputTokens": {
|
|
59
|
-
"type": "integer",
|
|
60
|
-
"exclusiveMinimum": 0,
|
|
61
|
-
"maximum": 9007199254740991
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
"additionalProperties": {}
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
"additionalProperties": false
|
|
68
|
-
}
|
|
69
|
-
},
|
|
70
|
-
"additionalProperties": false
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
"required": ["model", "apiKeyEnv"],
|
|
74
|
-
"additionalProperties": false
|
|
75
|
-
},
|
|
76
8
|
"cleaner": {
|
|
77
9
|
"default": {
|
|
78
10
|
"whitelist": ["//!"],
|
|
@@ -156,82 +88,11 @@
|
|
|
156
88
|
"prompts": {
|
|
157
89
|
"type": "object",
|
|
158
90
|
"properties": {
|
|
159
|
-
"review": {
|
|
160
|
-
"type": "object",
|
|
161
|
-
"propertyNames": {
|
|
162
|
-
"type": "string"
|
|
163
|
-
},
|
|
164
|
-
"additionalProperties": {
|
|
165
|
-
"type": "string"
|
|
166
|
-
}
|
|
167
|
-
},
|
|
168
|
-
"commit": {
|
|
169
|
-
"type": "string"
|
|
170
|
-
},
|
|
171
91
|
"pack": {
|
|
172
92
|
"type": "string"
|
|
173
93
|
}
|
|
174
94
|
},
|
|
175
95
|
"additionalProperties": false
|
|
176
|
-
},
|
|
177
|
-
"ops": {
|
|
178
|
-
"type": "object",
|
|
179
|
-
"properties": {
|
|
180
|
-
"servers": {
|
|
181
|
-
"type": "object",
|
|
182
|
-
"propertyNames": {
|
|
183
|
-
"type": "string"
|
|
184
|
-
},
|
|
185
|
-
"additionalProperties": {
|
|
186
|
-
"type": "object",
|
|
187
|
-
"properties": {
|
|
188
|
-
"host": {
|
|
189
|
-
"type": "string"
|
|
190
|
-
},
|
|
191
|
-
"port": {
|
|
192
|
-
"default": 22,
|
|
193
|
-
"type": "number"
|
|
194
|
-
},
|
|
195
|
-
"user": {
|
|
196
|
-
"type": "string"
|
|
197
|
-
},
|
|
198
|
-
"sshKeyPath": {
|
|
199
|
-
"type": "string"
|
|
200
|
-
},
|
|
201
|
-
"description": {
|
|
202
|
-
"type": "string"
|
|
203
|
-
},
|
|
204
|
-
"paths": {
|
|
205
|
-
"type": "object",
|
|
206
|
-
"properties": {
|
|
207
|
-
"apps": {
|
|
208
|
-
"default": "/var/agent-apps",
|
|
209
|
-
"type": "string"
|
|
210
|
-
},
|
|
211
|
-
"caddy": {
|
|
212
|
-
"type": "string"
|
|
213
|
-
}
|
|
214
|
-
},
|
|
215
|
-
"required": ["apps"],
|
|
216
|
-
"additionalProperties": false
|
|
217
|
-
},
|
|
218
|
-
"env": {
|
|
219
|
-
"type": "object",
|
|
220
|
-
"propertyNames": {
|
|
221
|
-
"type": "string"
|
|
222
|
-
},
|
|
223
|
-
"additionalProperties": {
|
|
224
|
-
"type": "string"
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
},
|
|
228
|
-
"required": ["host", "port", "user", "sshKeyPath"],
|
|
229
|
-
"additionalProperties": false
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
},
|
|
233
|
-
"required": ["servers"],
|
|
234
|
-
"additionalProperties": false
|
|
235
96
|
}
|
|
236
97
|
},
|
|
237
98
|
"required": ["cleaner", "packer"],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kodu",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "High-performance CLI to prepare codebase for LLMs, automate reviews, and draft commits.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -50,7 +50,6 @@
|
|
|
50
50
|
"@inquirer/confirm": "^6.0.4",
|
|
51
51
|
"@inquirer/input": "^5.0.4",
|
|
52
52
|
"@inquirer/select": "^5.0.4",
|
|
53
|
-
"@mastra/core": "^1.0.4",
|
|
54
53
|
"@nestjs/common": "^11.0.1",
|
|
55
54
|
"@nestjs/core": "^11.0.1",
|
|
56
55
|
"clipboardy": "^5.0.2",
|
package/src/app.module.ts
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import { Module } from '@nestjs/common';
|
|
2
2
|
import { CleanModule } from './commands/clean/clean.module';
|
|
3
|
-
import { CommitModule } from './commands/commit/commit.module';
|
|
4
3
|
import { InitModule } from './commands/init/init.module';
|
|
5
|
-
import { OpsModule } from './commands/ops/ops.module';
|
|
6
4
|
import { PackModule } from './commands/pack/pack.module';
|
|
7
|
-
import { ReviewModule } from './commands/review/review.module';
|
|
8
5
|
import { ConfigModule } from './core/config/config.module';
|
|
9
6
|
import { FsModule } from './core/file-system/fs.module';
|
|
10
7
|
import { UiModule } from './core/ui/ui.module';
|
|
11
|
-
import { AiModule } from './shared/ai/ai.module';
|
|
12
8
|
import { GitModule } from './shared/git/git.module';
|
|
13
9
|
import { TokenizerModule } from './shared/tokenizer/tokenizer.module';
|
|
14
10
|
|
|
@@ -18,14 +14,10 @@ import { TokenizerModule } from './shared/tokenizer/tokenizer.module';
|
|
|
18
14
|
UiModule,
|
|
19
15
|
FsModule,
|
|
20
16
|
GitModule,
|
|
21
|
-
AiModule,
|
|
22
17
|
TokenizerModule,
|
|
23
18
|
InitModule,
|
|
24
19
|
PackModule,
|
|
25
20
|
CleanModule,
|
|
26
|
-
ReviewModule,
|
|
27
|
-
CommitModule,
|
|
28
|
-
OpsModule,
|
|
29
21
|
],
|
|
30
22
|
})
|
|
31
23
|
export class AppModule {}
|
|
@@ -1,342 +1,47 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { Command, CommandRunner } from 'nest-commander';
|
|
4
|
-
import { type KoduConfig } from '../../core/config/config.schema';
|
|
5
|
-
import {
|
|
6
|
-
DEFAULT_COMMIT_PROMPT,
|
|
7
|
-
DEFAULT_PACK_PROMPT,
|
|
8
|
-
DEFAULT_REVIEW_PROMPTS,
|
|
9
|
-
} from '../../core/config/default-prompts';
|
|
10
4
|
import { UiService } from '../../core/ui/ui.service';
|
|
11
|
-
import {
|
|
12
|
-
DEFAULT_COMMIT_TOKENS,
|
|
13
|
-
DEFAULT_LLM_MODEL,
|
|
14
|
-
DEFAULT_REVIEW_TOKENS,
|
|
15
|
-
} from '../../shared/constants';
|
|
16
5
|
|
|
17
|
-
const
|
|
18
|
-
commit: { modelSettings: { maxOutputTokens: DEFAULT_COMMIT_TOKENS } },
|
|
19
|
-
review: { modelSettings: { maxOutputTokens: DEFAULT_REVIEW_TOKENS } },
|
|
20
|
-
});
|
|
6
|
+
const GITIGNORE_ENTRY = '.kodu/context.txt';
|
|
21
7
|
|
|
22
|
-
@Command({ name: 'init', description: '
|
|
8
|
+
@Command({ name: 'init', description: 'Add kodu output to .gitignore' })
|
|
23
9
|
export class InitCommand extends CommandRunner {
|
|
24
10
|
constructor(private readonly ui: UiService) {
|
|
25
11
|
super();
|
|
26
12
|
}
|
|
27
13
|
|
|
28
14
|
async run(): Promise<void> {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const defaultLlmConfig = {
|
|
32
|
-
model: `openai/${DEFAULT_LLM_MODEL}`,
|
|
33
|
-
apiKeyEnv: 'OPENAI_API_KEY',
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const defaultConfig: KoduConfig = {
|
|
37
|
-
$schema:
|
|
38
|
-
'https://raw.githubusercontent.com/uxname/kodu/refs/heads/master/kodu.schema.json',
|
|
39
|
-
llm: defaultLlmConfig,
|
|
40
|
-
ops: {
|
|
41
|
-
servers: {
|
|
42
|
-
dev: {
|
|
43
|
-
host: 'example.com',
|
|
44
|
-
port: 22,
|
|
45
|
-
user: 'ubuntu',
|
|
46
|
-
sshKeyPath: '~/.ssh/id_rsa',
|
|
47
|
-
description: 'Example AgentOps server',
|
|
48
|
-
paths: {
|
|
49
|
-
apps: '/var/agent-apps',
|
|
50
|
-
caddy: '/var/agent-apps/caddy',
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
},
|
|
55
|
-
cleaner: {
|
|
56
|
-
whitelist: ['//!'],
|
|
57
|
-
keepJSDoc: true,
|
|
58
|
-
useGitignore: true,
|
|
59
|
-
ignore: [],
|
|
60
|
-
},
|
|
61
|
-
packer: {
|
|
62
|
-
ignore: [
|
|
63
|
-
'package-lock.json',
|
|
64
|
-
'yarn.lock',
|
|
65
|
-
'pnpm-lock.yaml',
|
|
66
|
-
'.git',
|
|
67
|
-
'.kodu',
|
|
68
|
-
'node_modules',
|
|
69
|
-
'dist',
|
|
70
|
-
'coverage',
|
|
71
|
-
],
|
|
72
|
-
useGitignore: true,
|
|
73
|
-
contentBasedBinaryDetection: false,
|
|
74
|
-
},
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const useAi = await this.ui.promptConfirm({
|
|
78
|
-
message: 'Will you use AI functions?',
|
|
79
|
-
default: true,
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
let llmConfig: KoduConfig['llm'] | undefined;
|
|
83
|
-
if (useAi) {
|
|
84
|
-
const useCustomModel = await this.ui.promptConfirm({
|
|
85
|
-
message: 'Use your own model?',
|
|
86
|
-
default: false,
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
let model: string;
|
|
90
|
-
if (useCustomModel) {
|
|
91
|
-
model = await this.ui.promptInput({
|
|
92
|
-
message:
|
|
93
|
-
'Enter model in format provider/model-name (e.g., openai/gpt-4o):',
|
|
94
|
-
default: defaultLlmConfig.model,
|
|
95
|
-
validate: (input) => {
|
|
96
|
-
if (!input.includes('/')) {
|
|
97
|
-
return 'Model must be in format provider/model-name';
|
|
98
|
-
}
|
|
99
|
-
return true;
|
|
100
|
-
},
|
|
101
|
-
});
|
|
102
|
-
} else {
|
|
103
|
-
model = await this.ui.promptSelect<string>(
|
|
104
|
-
this.buildModelQuestion(defaultLlmConfig.model),
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
llmConfig = {
|
|
109
|
-
model,
|
|
110
|
-
apiKeyEnv: defaultLlmConfig.apiKeyEnv,
|
|
111
|
-
commands: buildDefaultCommandSettings(),
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const extendIgnore = await this.ui.promptConfirm({
|
|
116
|
-
message: 'Modify standard ignore list?',
|
|
117
|
-
default: false,
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
const ignoreList = extendIgnore
|
|
121
|
-
? await this.askIgnoreList(defaultConfig.packer.ignore)
|
|
122
|
-
: defaultConfig.packer.ignore;
|
|
123
|
-
|
|
124
|
-
const additionalWhitelist = await this.ui.promptInput({
|
|
125
|
-
message:
|
|
126
|
-
'Additional whitelist prefixes (comma-separated, empty - keep default):',
|
|
127
|
-
default: '',
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
const whitelist = this.mergeWhitelist(
|
|
131
|
-
defaultConfig.cleaner.whitelist,
|
|
132
|
-
additionalWhitelist,
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
const promptPaths = this.buildPromptPaths();
|
|
136
|
-
|
|
137
|
-
const configToSave: KoduConfig = {
|
|
138
|
-
$schema: defaultConfig.$schema,
|
|
139
|
-
...(llmConfig && { llm: llmConfig }),
|
|
140
|
-
cleaner: {
|
|
141
|
-
whitelist,
|
|
142
|
-
keepJSDoc: defaultConfig.cleaner.keepJSDoc,
|
|
143
|
-
useGitignore: defaultConfig.cleaner.useGitignore,
|
|
144
|
-
ignore: defaultConfig.cleaner.ignore,
|
|
145
|
-
},
|
|
146
|
-
packer: {
|
|
147
|
-
ignore: ignoreList,
|
|
148
|
-
useGitignore: defaultConfig.packer.useGitignore,
|
|
149
|
-
contentBasedBinaryDetection:
|
|
150
|
-
defaultConfig.packer.contentBasedBinaryDetection,
|
|
151
|
-
},
|
|
152
|
-
ops: defaultConfig.ops,
|
|
153
|
-
prompts: {
|
|
154
|
-
review: {
|
|
155
|
-
bug: promptPaths.review.bug,
|
|
156
|
-
style: promptPaths.review.style,
|
|
157
|
-
security: promptPaths.review.security,
|
|
158
|
-
},
|
|
159
|
-
commit: promptPaths.commit,
|
|
160
|
-
pack: promptPaths.pack,
|
|
161
|
-
},
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
await this.writeConfig(configPath, configToSave);
|
|
165
|
-
await this.ensurePromptFiles(promptPaths);
|
|
166
|
-
await this.ensureGitignore();
|
|
167
|
-
|
|
168
|
-
this.ui.log.success('Kodu configuration created.');
|
|
169
|
-
if (useAi) {
|
|
170
|
-
this.ui.log.info('🎉 Kodu initialized! Run `kodu pack` to continue.');
|
|
171
|
-
} else {
|
|
172
|
-
this.ui.log.info('🎉 Kodu initialized! Available commands: pack, clean.');
|
|
173
|
-
this.ui.log.info(
|
|
174
|
-
'To use AI functions (review, commit) add llm section to kodu.json.',
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
private buildModelQuestion(defaultModel: string) {
|
|
180
|
-
return {
|
|
181
|
-
message: 'Select AI model',
|
|
182
|
-
choices: [
|
|
183
|
-
{
|
|
184
|
-
name: 'OpenAI GPT-4o (recommended)',
|
|
185
|
-
value: `openai/${DEFAULT_LLM_MODEL}`,
|
|
186
|
-
},
|
|
187
|
-
{ name: 'OpenAI GPT-4o Mini', value: 'openai/gpt-4o-mini' },
|
|
188
|
-
{ name: 'OpenAI GPT-4o', value: 'openai/gpt-4o' },
|
|
189
|
-
{
|
|
190
|
-
name: 'Anthropic Claude 3.5 Sonnet',
|
|
191
|
-
value: 'anthropic/claude-3-5-sonnet-20241022',
|
|
192
|
-
},
|
|
193
|
-
{ name: 'Google Gemini 2.5 Flash', value: 'google/gemini-2.5-flash' },
|
|
194
|
-
],
|
|
195
|
-
default: defaultModel,
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
private async askIgnoreList(defaultIgnore: string[]): Promise<string[]> {
|
|
200
|
-
const answer = await this.ui.promptInput({
|
|
201
|
-
message: 'Specify ignore patterns (comma-separated)',
|
|
202
|
-
default: defaultIgnore.join(', '),
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
return answer
|
|
206
|
-
.split(',')
|
|
207
|
-
.map((item) => item.trim())
|
|
208
|
-
.filter((item) => item.length > 0);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
private mergeWhitelist(defaultWhitelist: string[], extra: string): string[] {
|
|
212
|
-
if (!extra.trim()) {
|
|
213
|
-
return defaultWhitelist;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const additions = extra
|
|
217
|
-
.split(',')
|
|
218
|
-
.map((item) => item.trim())
|
|
219
|
-
.filter((item) => item.length > 0);
|
|
220
|
-
|
|
221
|
-
return Array.from(new Set([...defaultWhitelist, ...additions]));
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
private async writeConfig(
|
|
225
|
-
configPath: string,
|
|
226
|
-
config: KoduConfig,
|
|
227
|
-
): Promise<void> {
|
|
228
|
-
if (await this.fileExists(configPath)) {
|
|
229
|
-
const overwrite = await this.ui.promptConfirm({
|
|
230
|
-
message: 'kodu.json already exists. Overwrite?',
|
|
231
|
-
default: false,
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
if (!overwrite) {
|
|
235
|
-
this.ui.log.warn(
|
|
236
|
-
'Initialization cancelled: kodu.json file already exists.',
|
|
237
|
-
);
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
await fs.writeFile(
|
|
243
|
-
configPath,
|
|
244
|
-
`${JSON.stringify(config, null, 2)}\n`,
|
|
245
|
-
'utf8',
|
|
246
|
-
);
|
|
247
|
-
this.ui.log.success(`Saved ${configPath}`);
|
|
15
|
+
await this.updateGitignore();
|
|
16
|
+
this.ui.log.success('Done.');
|
|
248
17
|
}
|
|
249
18
|
|
|
250
|
-
private async
|
|
251
|
-
|
|
252
|
-
): Promise<void> {
|
|
253
|
-
const promptDir = path.join(process.cwd(), '.kodu', 'prompts');
|
|
254
|
-
await fs.mkdir(promptDir, { recursive: true });
|
|
255
|
-
|
|
256
|
-
const keepFile = path.join(promptDir, '.keep');
|
|
257
|
-
if (!(await this.fileExists(keepFile))) {
|
|
258
|
-
await fs.writeFile(keepFile, '');
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
await Promise.all([
|
|
262
|
-
this.writePromptIfMissing(paths.review.bug, DEFAULT_REVIEW_PROMPTS.bug),
|
|
263
|
-
this.writePromptIfMissing(
|
|
264
|
-
paths.review.style,
|
|
265
|
-
DEFAULT_REVIEW_PROMPTS.style,
|
|
266
|
-
),
|
|
267
|
-
this.writePromptIfMissing(
|
|
268
|
-
paths.review.security,
|
|
269
|
-
DEFAULT_REVIEW_PROMPTS.security,
|
|
270
|
-
),
|
|
271
|
-
this.writePromptIfMissing(paths.commit, DEFAULT_COMMIT_PROMPT),
|
|
272
|
-
this.writePromptIfMissing(paths.pack, DEFAULT_PACK_PROMPT),
|
|
273
|
-
]);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
private buildPromptPaths() {
|
|
277
|
-
return {
|
|
278
|
-
review: {
|
|
279
|
-
bug: path.posix.join('.kodu', 'prompts', 'review-bug.md'),
|
|
280
|
-
style: path.posix.join('.kodu', 'prompts', 'review-style.md'),
|
|
281
|
-
security: path.posix.join('.kodu', 'prompts', 'review-security.md'),
|
|
282
|
-
},
|
|
283
|
-
commit: path.posix.join('.kodu', 'prompts', 'commit.md'),
|
|
284
|
-
pack: path.posix.join('.kodu', 'prompts', 'pack.md'),
|
|
285
|
-
} as const;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
private async writePromptIfMissing(
|
|
289
|
-
target: string,
|
|
290
|
-
content: string,
|
|
291
|
-
): Promise<void> {
|
|
292
|
-
const absolute = path.isAbsolute(target)
|
|
293
|
-
? target
|
|
294
|
-
: path.join(process.cwd(), target);
|
|
19
|
+
private async updateGitignore(): Promise<void> {
|
|
20
|
+
const gitignorePath = path.join(process.cwd(), '.gitignore');
|
|
295
21
|
|
|
296
|
-
if (await this.
|
|
22
|
+
if (!(await this.exists(gitignorePath))) {
|
|
23
|
+
this.ui.log.warn('.gitignore not found, skipping.');
|
|
297
24
|
return;
|
|
298
25
|
}
|
|
299
26
|
|
|
300
|
-
await fs.
|
|
301
|
-
await fs.writeFile(absolute, `${content}\n`, 'utf8');
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
private async ensureGitignore(): Promise<void> {
|
|
305
|
-
const gitignorePath = path.join(process.cwd(), '.gitignore');
|
|
306
|
-
const content = (await this.fileExists(gitignorePath))
|
|
307
|
-
? await fs.readFile(gitignorePath, 'utf8')
|
|
308
|
-
: '';
|
|
309
|
-
|
|
27
|
+
const content = await fs.readFile(gitignorePath, 'utf8');
|
|
310
28
|
const lines = content.split(/\r?\n/);
|
|
311
|
-
const additions: string[] = [];
|
|
312
|
-
|
|
313
|
-
if (!lines.some((line) => line.trim() === '.env')) {
|
|
314
|
-
const addEnv = await this.ui.promptConfirm({
|
|
315
|
-
message: '.env not in .gitignore. Add it?',
|
|
316
|
-
default: true,
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
if (addEnv) {
|
|
320
|
-
additions.push('.env');
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
29
|
|
|
324
|
-
if (
|
|
30
|
+
if (lines.some((line) => line.trim() === GITIGNORE_ENTRY)) {
|
|
31
|
+
this.ui.log.info(`${GITIGNORE_ENTRY} already in .gitignore`);
|
|
325
32
|
return;
|
|
326
33
|
}
|
|
327
34
|
|
|
328
35
|
const trimmed = content.trimEnd();
|
|
329
36
|
const next =
|
|
330
|
-
trimmed.length > 0
|
|
331
|
-
? `${trimmed}\n${additions.join('\n')}`
|
|
332
|
-
: additions.join('\n');
|
|
37
|
+
trimmed.length > 0 ? `${trimmed}\n${GITIGNORE_ENTRY}` : GITIGNORE_ENTRY;
|
|
333
38
|
await fs.writeFile(gitignorePath, `${next}\n`, 'utf8');
|
|
334
|
-
this.ui.log.success(
|
|
39
|
+
this.ui.log.success(`Added ${GITIGNORE_ENTRY} to .gitignore`);
|
|
335
40
|
}
|
|
336
41
|
|
|
337
|
-
private async
|
|
42
|
+
private async exists(target: string): Promise<boolean> {
|
|
338
43
|
try {
|
|
339
|
-
await fs.access(
|
|
44
|
+
await fs.access(target);
|
|
340
45
|
return true;
|
|
341
46
|
} catch {
|
|
342
47
|
return false;
|
|
@@ -8,11 +8,16 @@ import { FsService } from '../../core/file-system/fs.service';
|
|
|
8
8
|
import { UiService } from '../../core/ui/ui.service';
|
|
9
9
|
import { TokenizerService } from '../../shared/tokenizer/tokenizer.service';
|
|
10
10
|
|
|
11
|
+
type OutputFormat = 'xml' | 'text';
|
|
12
|
+
|
|
11
13
|
type PackOptions = {
|
|
12
14
|
copy?: boolean;
|
|
13
15
|
template?: string;
|
|
14
16
|
out?: string;
|
|
15
17
|
path?: string[];
|
|
18
|
+
exclude?: string[];
|
|
19
|
+
list?: boolean;
|
|
20
|
+
format?: OutputFormat;
|
|
16
21
|
};
|
|
17
22
|
|
|
18
23
|
type TemplateContext = {
|
|
@@ -66,6 +71,34 @@ export class PackCommand extends CommandRunner {
|
|
|
66
71
|
return [...previous, value];
|
|
67
72
|
}
|
|
68
73
|
|
|
74
|
+
@Option({
|
|
75
|
+
flags: '-e, --exclude <pattern>',
|
|
76
|
+
description: 'Additional exclude pattern (repeatable)',
|
|
77
|
+
})
|
|
78
|
+
parseExclude(value: string, previous: string[] = []): string[] {
|
|
79
|
+
return [...previous, value];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@Option({
|
|
83
|
+
flags: '-l, --list',
|
|
84
|
+
description: 'Print file list only, without content',
|
|
85
|
+
})
|
|
86
|
+
parseList(): boolean {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@Option({
|
|
91
|
+
flags: '-f, --format <format>',
|
|
92
|
+
description: 'Output format: xml (default) or text',
|
|
93
|
+
})
|
|
94
|
+
parseFormat(value: string): OutputFormat {
|
|
95
|
+
if (value !== 'xml' && value !== 'text') {
|
|
96
|
+
this.ui.log.warn(`Unknown format "${value}", using "xml"`);
|
|
97
|
+
return 'xml';
|
|
98
|
+
}
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
|
|
69
102
|
async run(_inputs: string[], options: PackOptions): Promise<void> {
|
|
70
103
|
const spinner = this.ui
|
|
71
104
|
.createSpinner({ text: 'Collecting files...' })
|
|
@@ -73,10 +106,11 @@ export class PackCommand extends CommandRunner {
|
|
|
73
106
|
|
|
74
107
|
try {
|
|
75
108
|
const { packer } = this.configService.getConfig();
|
|
109
|
+
const extraExcludes = options.exclude ?? [];
|
|
76
110
|
const files = await this.fsService.findProjectFiles({
|
|
77
111
|
excludeBinary: true,
|
|
78
112
|
useGitignore: packer.useGitignore,
|
|
79
|
-
ignore: packer.ignore,
|
|
113
|
+
ignore: [...packer.ignore, ...extraExcludes],
|
|
80
114
|
contentBasedBinaryDetection: packer.contentBasedBinaryDetection,
|
|
81
115
|
rootPaths: options.path,
|
|
82
116
|
});
|
|
@@ -87,7 +121,16 @@ export class PackCommand extends CommandRunner {
|
|
|
87
121
|
return;
|
|
88
122
|
}
|
|
89
123
|
|
|
90
|
-
|
|
124
|
+
if (options.list) {
|
|
125
|
+
spinner.success(`Found ${files.length} files`);
|
|
126
|
+
for (const file of files) {
|
|
127
|
+
this.ui.log.info(file);
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const format: OutputFormat = options.format ?? 'xml';
|
|
133
|
+
const context = await this.buildContext(files, format);
|
|
91
134
|
const fileList = files.join('\n');
|
|
92
135
|
const { tokens, usdEstimate } = this.tokenizer.count(context);
|
|
93
136
|
|
|
@@ -117,6 +160,7 @@ export class PackCommand extends CommandRunner {
|
|
|
117
160
|
this.ui.log.info(`Files: ${files.length}`);
|
|
118
161
|
this.ui.log.info(`Tokens: ${tokens}`);
|
|
119
162
|
this.ui.log.info(`Cost estimate: ~$${usdEstimate.toFixed(4)}`);
|
|
163
|
+
this.ui.log.info(`Format: ${format}`);
|
|
120
164
|
this.ui.log.success(`Saved to ${outputPath}`);
|
|
121
165
|
|
|
122
166
|
if (options.copy) {
|
|
@@ -130,14 +174,23 @@ export class PackCommand extends CommandRunner {
|
|
|
130
174
|
}
|
|
131
175
|
}
|
|
132
176
|
|
|
133
|
-
private async buildContext(
|
|
177
|
+
private async buildContext(
|
|
178
|
+
files: string[],
|
|
179
|
+
format: OutputFormat,
|
|
180
|
+
): Promise<string> {
|
|
134
181
|
const chunks = await Promise.all(
|
|
135
182
|
files.map(async (file) => {
|
|
136
183
|
const content = await this.fsService.readFileRelative(file);
|
|
184
|
+
if (format === 'xml') {
|
|
185
|
+
return `<file path="${file}">\n${content}\n</file>`;
|
|
186
|
+
}
|
|
137
187
|
return `// file: ${file}\n${content}`;
|
|
138
188
|
}),
|
|
139
189
|
);
|
|
140
190
|
|
|
191
|
+
if (format === 'xml') {
|
|
192
|
+
return `<files>\n${chunks.join('\n\n')}\n</files>`;
|
|
193
|
+
}
|
|
141
194
|
return chunks.join('\n\n');
|
|
142
195
|
}
|
|
143
196
|
|