thomas-agentkit 0.3.0 → 0.4.2
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 +52 -6
- package/dist/cli.js +509 -37
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,16 +14,26 @@ Install templates into the current directory:
|
|
|
14
14
|
npx thomas-agentkit init
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
By default, `init` opens a short interactive setup flow in terminals. After choosing install options, you can optionally personalize repository-level template placeholders such as project name, description, issue tracker, docs paths, stack summary, and project commands.
|
|
18
|
+
|
|
17
19
|
Install into another directory:
|
|
18
20
|
|
|
19
21
|
```bash
|
|
20
22
|
npx thomas-agentkit init ./my-project
|
|
21
23
|
```
|
|
22
24
|
|
|
25
|
+
Accept defaults without prompts:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx thomas-agentkit init --yes
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`--yes` keeps installed templates generic and leaves placeholders for later editing.
|
|
32
|
+
|
|
23
33
|
Preview changes without writing files:
|
|
24
34
|
|
|
25
35
|
```bash
|
|
26
|
-
npx thomas-agentkit init --dry-run
|
|
36
|
+
npx thomas-agentkit init --yes --dry-run
|
|
27
37
|
```
|
|
28
38
|
|
|
29
39
|
Overwrite existing files:
|
|
@@ -44,7 +54,7 @@ Preview updates without writing files:
|
|
|
44
54
|
npx thomas-agentkit update --dry-run
|
|
45
55
|
```
|
|
46
56
|
|
|
47
|
-
|
|
57
|
+
Explicitly request the interactive flow:
|
|
48
58
|
|
|
49
59
|
```bash
|
|
50
60
|
npx thomas-agentkit init --interactive
|
|
@@ -56,6 +66,37 @@ Install stack-specific agent guidance:
|
|
|
56
66
|
npx thomas-agentkit init --preset next
|
|
57
67
|
```
|
|
58
68
|
|
|
69
|
+
Standardize install defaults with `agentkit.config.json`:
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"preset": "next",
|
|
74
|
+
"templateSet": "standard",
|
|
75
|
+
"aiTools": ["codex", "cursor", "claude"],
|
|
76
|
+
"personalization": {
|
|
77
|
+
"projectName": "Acme CRM",
|
|
78
|
+
"projectDescription": "a customer operations dashboard",
|
|
79
|
+
"issueTracker": "Linear",
|
|
80
|
+
"designSystemPath": "docs/design-system.md",
|
|
81
|
+
"briefsPath": "docs/briefs",
|
|
82
|
+
"testCommand": "pnpm test",
|
|
83
|
+
"lintCommand": "pnpm lint",
|
|
84
|
+
"buildCommand": "pnpm build",
|
|
85
|
+
"stackSummary": "Next.js, TypeScript, PostgreSQL"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
AgentKit reads `agentkit.config.json` from the target directory first. When installing into another target that does not have a config file, it falls back to the current working directory. Config values are defaults: explicit CLI flags override them.
|
|
91
|
+
|
|
92
|
+
Create a config file from resolved install choices:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
npx thomas-agentkit init --write-config
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`--write-config` writes `agentkit.config.json` in the target directory. Existing config files are skipped by default; use `--force` to overwrite one intentionally.
|
|
99
|
+
|
|
59
100
|
List bundled templates:
|
|
60
101
|
|
|
61
102
|
```bash
|
|
@@ -97,6 +138,10 @@ Generated content
|
|
|
97
138
|
|
|
98
139
|
`agentkit update` only replaces content inside matching managed blocks. User edits before or after those blocks are preserved. Existing legacy files without managed blocks are reported as unmanaged and left untouched.
|
|
99
140
|
|
|
141
|
+
Interactive personalization only applies during `agentkit init` when files are created or overwritten. It does not write a config file, and `agentkit update` does not reapply personalized values.
|
|
142
|
+
|
|
143
|
+
`agentkit.config.json` can set `preset`, `templateSet`, `aiTools`, and `personalization` defaults. `templateSet` may be `minimal`, `standard`, or `full`; `aiTools` may include `codex`, `cursor`, `claude`, and `copilot`. `agentkit update` only uses config `preset` and continues to update all managed bundled templates.
|
|
144
|
+
|
|
100
145
|
## Presets
|
|
101
146
|
|
|
102
147
|
Presets add stack-specific guidance without scaffolding framework app files.
|
|
@@ -110,7 +155,7 @@ Presets add stack-specific guidance without scaffolding framework app files.
|
|
|
110
155
|
## CLI Reference
|
|
111
156
|
|
|
112
157
|
```text
|
|
113
|
-
agentkit init [target] [--force] [--dry-run] [--interactive] [--yes] [--preset <name>]
|
|
158
|
+
agentkit init [target] [--force] [--dry-run] [--interactive] [--yes] [--write-config] [--preset <name>]
|
|
114
159
|
agentkit update [target] [--dry-run] [--preset <name>]
|
|
115
160
|
agentkit --list
|
|
116
161
|
agentkit --list-presets
|
|
@@ -122,8 +167,9 @@ Options:
|
|
|
122
167
|
|
|
123
168
|
- `--force`: overwrite existing files
|
|
124
169
|
- `--dry-run`: print planned changes without writing files
|
|
125
|
-
- `-i, --interactive`: prompt for install options
|
|
126
|
-
- `-y, --yes`: accept defaults
|
|
170
|
+
- `-i, --interactive`: explicitly prompt for install options
|
|
171
|
+
- `-y, --yes`: accept defaults without prompts
|
|
172
|
+
- `--write-config`: write resolved install defaults to `agentkit.config.json`
|
|
127
173
|
- `--preset <name>`: install stack-specific guidance (`next`, `sveltekit`, `express`, `convex`, `fullstack`)
|
|
128
174
|
- `--list`: list bundled template files
|
|
129
175
|
- `--list-presets`: list available presets
|
|
@@ -136,7 +182,7 @@ For `agentkit update`, `--preset <name>` refreshes preset-specific managed conte
|
|
|
136
182
|
|
|
137
183
|
```bash
|
|
138
184
|
npm install
|
|
139
|
-
npm run dev -- init ./tmp-demo --dry-run
|
|
185
|
+
npm run dev -- init ./tmp-demo --yes --dry-run
|
|
140
186
|
npm run build
|
|
141
187
|
npm test
|
|
142
188
|
```
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { confirm, isCancel, select, text } from "@clack/prompts";
|
|
2
|
+
import { confirm, intro, isCancel, multiselect, select, text } from "@clack/prompts";
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import { constants as fsConstants } from "node:fs";
|
|
5
5
|
import { access, mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
@@ -11,6 +11,22 @@ const packageRoot = path.resolve(__dirname, "..");
|
|
|
11
11
|
const templatesDir = path.join(packageRoot, "templates");
|
|
12
12
|
const packageJsonPath = path.join(packageRoot, "package.json");
|
|
13
13
|
const validPresets = ["next", "sveltekit", "express", "convex", "fullstack"];
|
|
14
|
+
const validProjectTypes = ["generic", ...validPresets];
|
|
15
|
+
const validAiTools = ["codex", "cursor", "claude", "copilot"];
|
|
16
|
+
const validTemplateSets = ["minimal", "standard", "full"];
|
|
17
|
+
const configFileName = "agentkit.config.json";
|
|
18
|
+
const configKeys = ["preset", "templateSet", "aiTools", "personalization"];
|
|
19
|
+
const personalizationKeys = [
|
|
20
|
+
"projectName",
|
|
21
|
+
"projectDescription",
|
|
22
|
+
"issueTracker",
|
|
23
|
+
"designSystemPath",
|
|
24
|
+
"briefsPath",
|
|
25
|
+
"testCommand",
|
|
26
|
+
"lintCommand",
|
|
27
|
+
"buildCommand",
|
|
28
|
+
"stackSummary",
|
|
29
|
+
];
|
|
14
30
|
const presetLabels = {
|
|
15
31
|
next: "Next.js",
|
|
16
32
|
sveltekit: "SvelteKit",
|
|
@@ -18,6 +34,17 @@ const presetLabels = {
|
|
|
18
34
|
convex: "Convex",
|
|
19
35
|
fullstack: "Fullstack",
|
|
20
36
|
};
|
|
37
|
+
const aiToolFiles = {
|
|
38
|
+
codex: ["AGENTS.md"],
|
|
39
|
+
cursor: [".cursor/rules/agentkit.md"],
|
|
40
|
+
claude: ["CLAUDE.md"],
|
|
41
|
+
copilot: [".github/copilot-instructions.md"],
|
|
42
|
+
};
|
|
43
|
+
const templateSetFiles = {
|
|
44
|
+
minimal: ["AGENTS.md"],
|
|
45
|
+
standard: ["AGENTS.md", "CODE-QUALITY.md", "DESIGN-SYSTEM.md", "WORKFLOWS.md"],
|
|
46
|
+
full: [],
|
|
47
|
+
};
|
|
21
48
|
const stackGuidance = {
|
|
22
49
|
next: `# Stack Guidance
|
|
23
50
|
|
|
@@ -101,9 +128,21 @@ async function getTemplateFiles(dir = templatesDir, base = templatesDir) {
|
|
|
101
128
|
function isPresetName(value) {
|
|
102
129
|
return validPresets.includes(value);
|
|
103
130
|
}
|
|
131
|
+
function isProjectTypeName(value) {
|
|
132
|
+
return validProjectTypes.includes(value);
|
|
133
|
+
}
|
|
134
|
+
function isAiToolName(value) {
|
|
135
|
+
return validAiTools.includes(value);
|
|
136
|
+
}
|
|
137
|
+
function isTemplateSetName(value) {
|
|
138
|
+
return validTemplateSets.includes(value);
|
|
139
|
+
}
|
|
104
140
|
function formatPresetList() {
|
|
105
141
|
return validPresets.join(", ");
|
|
106
142
|
}
|
|
143
|
+
function formatTemplateSetList() {
|
|
144
|
+
return validTemplateSets.join(", ");
|
|
145
|
+
}
|
|
107
146
|
function resolvePreset(preset) {
|
|
108
147
|
if (!preset) {
|
|
109
148
|
return undefined;
|
|
@@ -114,6 +153,147 @@ function resolvePreset(preset) {
|
|
|
114
153
|
}
|
|
115
154
|
return normalizedPreset;
|
|
116
155
|
}
|
|
156
|
+
function resolveTemplateSet(templateSet) {
|
|
157
|
+
if (!templateSet) {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
const normalizedTemplateSet = templateSet.toLowerCase();
|
|
161
|
+
if (!isTemplateSetName(normalizedTemplateSet)) {
|
|
162
|
+
throw new Error(`Unknown template set "${templateSet}". Valid template sets: ${formatTemplateSetList()}.`);
|
|
163
|
+
}
|
|
164
|
+
return normalizedTemplateSet;
|
|
165
|
+
}
|
|
166
|
+
function assertPlainObject(value, name) {
|
|
167
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
168
|
+
throw new Error(`${name} must be an object.`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function assertKnownKeys(value, validKeys, name) {
|
|
172
|
+
for (const key of Object.keys(value)) {
|
|
173
|
+
if (!validKeys.includes(key)) {
|
|
174
|
+
throw new Error(`Unknown ${name} key "${key}". Valid keys: ${validKeys.join(", ")}.`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function parseConfig(rawConfig, configPath) {
|
|
179
|
+
assertPlainObject(rawConfig, configFileName);
|
|
180
|
+
assertKnownKeys(rawConfig, configKeys, configFileName);
|
|
181
|
+
const config = {};
|
|
182
|
+
if (rawConfig.preset !== undefined) {
|
|
183
|
+
if (typeof rawConfig.preset !== "string") {
|
|
184
|
+
throw new Error(`${configFileName} preset must be a string.`);
|
|
185
|
+
}
|
|
186
|
+
config.preset = resolvePreset(rawConfig.preset);
|
|
187
|
+
}
|
|
188
|
+
if (rawConfig.templateSet !== undefined) {
|
|
189
|
+
if (typeof rawConfig.templateSet !== "string") {
|
|
190
|
+
throw new Error(`${configFileName} templateSet must be a string.`);
|
|
191
|
+
}
|
|
192
|
+
config.templateSet = resolveTemplateSet(rawConfig.templateSet);
|
|
193
|
+
}
|
|
194
|
+
if (rawConfig.aiTools !== undefined) {
|
|
195
|
+
if (!Array.isArray(rawConfig.aiTools)) {
|
|
196
|
+
throw new Error(`${configFileName} aiTools must be an array.`);
|
|
197
|
+
}
|
|
198
|
+
config.aiTools = rawConfig.aiTools.map((aiTool) => {
|
|
199
|
+
if (typeof aiTool !== "string" || !isAiToolName(aiTool)) {
|
|
200
|
+
throw new Error(`Unknown AI tool "${String(aiTool)}". Valid AI tools: ${validAiTools.join(", ")}.`);
|
|
201
|
+
}
|
|
202
|
+
return aiTool;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
if (rawConfig.personalization !== undefined) {
|
|
206
|
+
assertPlainObject(rawConfig.personalization, `${configFileName} personalization`);
|
|
207
|
+
assertKnownKeys(rawConfig.personalization, personalizationKeys, `${configFileName} personalization`);
|
|
208
|
+
config.personalization = {};
|
|
209
|
+
for (const [key, value] of Object.entries(rawConfig.personalization)) {
|
|
210
|
+
if (typeof value !== "string") {
|
|
211
|
+
throw new Error(`${configFileName} personalization.${key} must be a string.`);
|
|
212
|
+
}
|
|
213
|
+
config.personalization[key] = value;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (config.preset === undefined && rawConfig.preset !== undefined) {
|
|
217
|
+
throw new Error(`Invalid preset in ${configPath}.`);
|
|
218
|
+
}
|
|
219
|
+
return config;
|
|
220
|
+
}
|
|
221
|
+
async function readConfig(configPath) {
|
|
222
|
+
let rawContent;
|
|
223
|
+
try {
|
|
224
|
+
rawContent = await readFile(configPath, "utf8");
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
throw new Error(`Unable to read ${configPath}.`);
|
|
228
|
+
}
|
|
229
|
+
try {
|
|
230
|
+
return parseConfig(JSON.parse(rawContent), configPath);
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
234
|
+
throw new Error(`Invalid ${configPath}: ${message}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
async function loadConfigForTarget(targetArg) {
|
|
238
|
+
const cwd = process.cwd();
|
|
239
|
+
const targetDir = path.resolve(cwd, targetArg || ".");
|
|
240
|
+
const targetConfigPath = path.join(targetDir, configFileName);
|
|
241
|
+
if (await exists(targetConfigPath)) {
|
|
242
|
+
return readConfig(targetConfigPath);
|
|
243
|
+
}
|
|
244
|
+
const cwdConfigPath = path.join(cwd, configFileName);
|
|
245
|
+
if (targetDir !== cwd && (await exists(cwdConfigPath))) {
|
|
246
|
+
return readConfig(cwdConfigPath);
|
|
247
|
+
}
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
250
|
+
async function applyInitConfig(options, config) {
|
|
251
|
+
if (!config) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
options.preset ??= config.preset;
|
|
255
|
+
options.personalization ??= config.personalization;
|
|
256
|
+
options.templateSet ??= config.templateSet;
|
|
257
|
+
options.aiTools ??= config.aiTools;
|
|
258
|
+
}
|
|
259
|
+
function applyUpdateConfig(options, config) {
|
|
260
|
+
if (!config) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
options.preset ??= config.preset;
|
|
264
|
+
}
|
|
265
|
+
export function resolveProjectPreset(projectType) {
|
|
266
|
+
if (!projectType) {
|
|
267
|
+
return undefined;
|
|
268
|
+
}
|
|
269
|
+
const normalizedProjectType = projectType.toLowerCase();
|
|
270
|
+
if (!isProjectTypeName(normalizedProjectType)) {
|
|
271
|
+
throw new Error(`Unknown project type "${projectType}". Valid project types: ${validProjectTypes.join(", ")}.`);
|
|
272
|
+
}
|
|
273
|
+
return normalizedProjectType === "generic" ? undefined : normalizedProjectType;
|
|
274
|
+
}
|
|
275
|
+
export function getFilesForAiTools(aiTools) {
|
|
276
|
+
const files = new Set();
|
|
277
|
+
for (const aiTool of aiTools) {
|
|
278
|
+
if (!isAiToolName(aiTool)) {
|
|
279
|
+
throw new Error(`Unknown AI tool "${aiTool}". Valid AI tools: ${validAiTools.join(", ")}.`);
|
|
280
|
+
}
|
|
281
|
+
for (const file of aiToolFiles[aiTool]) {
|
|
282
|
+
files.add(file);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return [...files].sort();
|
|
286
|
+
}
|
|
287
|
+
export function getFilesForTemplateSet(templateSet, allTemplateFiles) {
|
|
288
|
+
if (templateSet === "full") {
|
|
289
|
+
return [...allTemplateFiles].sort();
|
|
290
|
+
}
|
|
291
|
+
return templateSetFiles[templateSet].filter((file) => allTemplateFiles.includes(file)).sort();
|
|
292
|
+
}
|
|
293
|
+
export function getSelectedTemplateFiles(templateSet, aiTools, allTemplateFiles) {
|
|
294
|
+
const files = new Set([...getFilesForTemplateSet(templateSet, allTemplateFiles), ...getFilesForAiTools(aiTools)]);
|
|
295
|
+
return [...files].filter((file) => allTemplateFiles.includes(file)).sort();
|
|
296
|
+
}
|
|
117
297
|
function getStackGuidance(preset) {
|
|
118
298
|
return preset === "fullstack" ? fullstackGuidance : stackGuidance[preset];
|
|
119
299
|
}
|
|
@@ -140,17 +320,249 @@ function addStackReference(file, content, preset) {
|
|
|
140
320
|
}
|
|
141
321
|
return `${content.trimEnd()}\n${stackNote}`;
|
|
142
322
|
}
|
|
323
|
+
function cleanPersonalizationValue(value) {
|
|
324
|
+
const trimmed = value?.trim();
|
|
325
|
+
return trimmed ? trimmed : undefined;
|
|
326
|
+
}
|
|
327
|
+
function getResolvedConfig(options) {
|
|
328
|
+
const config = {
|
|
329
|
+
templateSet: options.templateSet ?? "full",
|
|
330
|
+
aiTools: options.aiTools ?? [],
|
|
331
|
+
};
|
|
332
|
+
const preset = resolvePreset(options.preset);
|
|
333
|
+
if (preset) {
|
|
334
|
+
config.preset = preset;
|
|
335
|
+
}
|
|
336
|
+
if (options.personalization) {
|
|
337
|
+
const personalization = {};
|
|
338
|
+
for (const key of personalizationKeys) {
|
|
339
|
+
const value = cleanPersonalizationValue(options.personalization[key]);
|
|
340
|
+
if (value) {
|
|
341
|
+
personalization[key] = value;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (Object.keys(personalization).length > 0) {
|
|
345
|
+
config.personalization = personalization;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return config;
|
|
349
|
+
}
|
|
350
|
+
function serializeConfig(config) {
|
|
351
|
+
return `${JSON.stringify(config, null, 2)}\n`;
|
|
352
|
+
}
|
|
353
|
+
function replaceIfProvided(content, placeholder, value) {
|
|
354
|
+
const replacement = cleanPersonalizationValue(value);
|
|
355
|
+
return replacement ? content.replaceAll(placeholder, replacement) : content;
|
|
356
|
+
}
|
|
357
|
+
function getProvidedCommands(values) {
|
|
358
|
+
return [values.testCommand, values.lintCommand, values.buildCommand]
|
|
359
|
+
.map(cleanPersonalizationValue)
|
|
360
|
+
.filter((command) => Boolean(command));
|
|
361
|
+
}
|
|
362
|
+
function commandDescription(command, kind) {
|
|
363
|
+
const descriptions = {
|
|
364
|
+
test: "Run tests",
|
|
365
|
+
lint: "Run lint checks",
|
|
366
|
+
build: "Build or check the project",
|
|
367
|
+
};
|
|
368
|
+
return descriptions[kind] ?? command;
|
|
369
|
+
}
|
|
370
|
+
function replaceCommandBlock(content, commands) {
|
|
371
|
+
if (commands.length === 0) {
|
|
372
|
+
return content;
|
|
373
|
+
}
|
|
374
|
+
const commandBlock = ["```bash", ...commands, "```"].join("\n");
|
|
375
|
+
return content
|
|
376
|
+
.replace(/```bash\nnpm install\nnpm test\nnpm run build\nnpm run lint\n```/, commandBlock)
|
|
377
|
+
.replace(/```bash\nnpm test\nnpm run lint\nnpm run build\n```/, commandBlock);
|
|
378
|
+
}
|
|
379
|
+
function replaceAgentCommandTable(content, values) {
|
|
380
|
+
const rows = [
|
|
381
|
+
cleanPersonalizationValue(values.testCommand)
|
|
382
|
+
? `| \`${cleanPersonalizationValue(values.testCommand)}\` | ${commandDescription(values.testCommand ?? "", "test")} |`
|
|
383
|
+
: undefined,
|
|
384
|
+
cleanPersonalizationValue(values.lintCommand)
|
|
385
|
+
? `| \`${cleanPersonalizationValue(values.lintCommand)}\` | ${commandDescription(values.lintCommand ?? "", "lint")} |`
|
|
386
|
+
: undefined,
|
|
387
|
+
cleanPersonalizationValue(values.buildCommand)
|
|
388
|
+
? `| \`${cleanPersonalizationValue(values.buildCommand)}\` | ${commandDescription(values.buildCommand ?? "", "build")} |`
|
|
389
|
+
: undefined,
|
|
390
|
+
].filter((row) => Boolean(row));
|
|
391
|
+
if (rows.length === 0) {
|
|
392
|
+
return content;
|
|
393
|
+
}
|
|
394
|
+
const nextTable = ["| Command | Description |", "| --- | --- |", ...rows].join("\n");
|
|
395
|
+
return content.replace(/\| Command \| Description \|\n\| --- \| --- \|\n\| `npm run dev` \| Start the local development server \|\n\| `npm test` \| Run tests \|\n\| `npm run lint` \| Run lint checks \|\n\| `npm run build` \| Build the project \|/, nextTable);
|
|
396
|
+
}
|
|
397
|
+
function replaceStackSummary(content, stackSummary) {
|
|
398
|
+
const summary = cleanPersonalizationValue(stackSummary);
|
|
399
|
+
if (!summary) {
|
|
400
|
+
return content;
|
|
401
|
+
}
|
|
402
|
+
const stackItems = summary
|
|
403
|
+
.split(/\r?\n|,/)
|
|
404
|
+
.map((item) => item.trim())
|
|
405
|
+
.filter(Boolean)
|
|
406
|
+
.map((item) => `- ${item}`)
|
|
407
|
+
.join("\n");
|
|
408
|
+
return content.replace(/- \[Primary framework\]\n- \[Language\/runtime\]\n- \[Backend\/data layer\]\n- \[Styling system\]\n- \[Test tools\]\n- \[Lint\/format tools\]/, stackItems);
|
|
409
|
+
}
|
|
410
|
+
export function personalizeTemplateContent(file, content, values) {
|
|
411
|
+
if (!values) {
|
|
412
|
+
return content;
|
|
413
|
+
}
|
|
414
|
+
if (file === "PRD-TEMPLATE.md" ||
|
|
415
|
+
file === "IMPLEMENTATION-BRIEF-TEMPLATE.md" ||
|
|
416
|
+
file === ".github/pull_request_template.md") {
|
|
417
|
+
return content;
|
|
418
|
+
}
|
|
419
|
+
let personalized = content;
|
|
420
|
+
if (file === "AGENTS.md" || file === "DESIGN-SYSTEM.md") {
|
|
421
|
+
personalized = replaceIfProvided(personalized, "[Project Name]", values.projectName);
|
|
422
|
+
}
|
|
423
|
+
if (file === "AGENTS.md") {
|
|
424
|
+
personalized = replaceIfProvided(personalized, "[short project description]", values.projectDescription);
|
|
425
|
+
personalized = replaceIfProvided(personalized, "[issue tracker, e.g. Linear or GitHub Issues]", values.issueTracker);
|
|
426
|
+
personalized = replaceIfProvided(personalized, "[design system path, e.g. docs/design-system.md]", values.designSystemPath);
|
|
427
|
+
personalized = replaceIfProvided(personalized, "[design system path]", values.designSystemPath);
|
|
428
|
+
personalized = replaceIfProvided(personalized, "[briefs path, e.g. docs/briefs]", values.briefsPath);
|
|
429
|
+
personalized = replaceIfProvided(personalized, "[test command, e.g. npm test]", values.testCommand);
|
|
430
|
+
personalized = replaceIfProvided(personalized, "[lint command, e.g. npm run lint]", values.lintCommand);
|
|
431
|
+
personalized = replaceIfProvided(personalized, "[build/check command, e.g. npm run build]", values.buildCommand);
|
|
432
|
+
personalized = replaceAgentCommandTable(personalized, values);
|
|
433
|
+
personalized = replaceStackSummary(personalized, values.stackSummary);
|
|
434
|
+
}
|
|
435
|
+
if (file === "CLAUDE.md" || file === "CODE-QUALITY.md") {
|
|
436
|
+
personalized = replaceCommandBlock(personalized, getProvidedCommands(values));
|
|
437
|
+
}
|
|
438
|
+
return personalized;
|
|
439
|
+
}
|
|
440
|
+
async function promptForPersonalization(defaults) {
|
|
441
|
+
const shouldPersonalize = await confirm({
|
|
442
|
+
message: "Personalize template placeholders?",
|
|
443
|
+
initialValue: Boolean(defaults),
|
|
444
|
+
});
|
|
445
|
+
if (isCancel(shouldPersonalize)) {
|
|
446
|
+
process.exit(130);
|
|
447
|
+
}
|
|
448
|
+
if (!shouldPersonalize) {
|
|
449
|
+
return undefined;
|
|
450
|
+
}
|
|
451
|
+
const projectName = await text({
|
|
452
|
+
message: "Project name",
|
|
453
|
+
placeholder: "[Project Name]",
|
|
454
|
+
defaultValue: defaults?.projectName,
|
|
455
|
+
});
|
|
456
|
+
if (isCancel(projectName)) {
|
|
457
|
+
process.exit(130);
|
|
458
|
+
}
|
|
459
|
+
const projectDescription = await text({
|
|
460
|
+
message: "Short project description",
|
|
461
|
+
placeholder: "[short project description]",
|
|
462
|
+
defaultValue: defaults?.projectDescription,
|
|
463
|
+
});
|
|
464
|
+
if (isCancel(projectDescription)) {
|
|
465
|
+
process.exit(130);
|
|
466
|
+
}
|
|
467
|
+
const issueTracker = await text({
|
|
468
|
+
message: "Issue tracker name",
|
|
469
|
+
placeholder: "Linear or GitHub Issues",
|
|
470
|
+
defaultValue: defaults?.issueTracker,
|
|
471
|
+
});
|
|
472
|
+
if (isCancel(issueTracker)) {
|
|
473
|
+
process.exit(130);
|
|
474
|
+
}
|
|
475
|
+
const designSystemPath = await text({
|
|
476
|
+
message: "Design system path",
|
|
477
|
+
placeholder: "docs/design-system.md",
|
|
478
|
+
defaultValue: defaults?.designSystemPath,
|
|
479
|
+
});
|
|
480
|
+
if (isCancel(designSystemPath)) {
|
|
481
|
+
process.exit(130);
|
|
482
|
+
}
|
|
483
|
+
const briefsPath = await text({
|
|
484
|
+
message: "Briefs path",
|
|
485
|
+
placeholder: "docs/briefs",
|
|
486
|
+
defaultValue: defaults?.briefsPath,
|
|
487
|
+
});
|
|
488
|
+
if (isCancel(briefsPath)) {
|
|
489
|
+
process.exit(130);
|
|
490
|
+
}
|
|
491
|
+
const testCommand = await text({
|
|
492
|
+
message: "Test command",
|
|
493
|
+
placeholder: "npm test",
|
|
494
|
+
defaultValue: defaults?.testCommand,
|
|
495
|
+
});
|
|
496
|
+
if (isCancel(testCommand)) {
|
|
497
|
+
process.exit(130);
|
|
498
|
+
}
|
|
499
|
+
const lintCommand = await text({
|
|
500
|
+
message: "Lint command",
|
|
501
|
+
placeholder: "npm run lint",
|
|
502
|
+
defaultValue: defaults?.lintCommand,
|
|
503
|
+
});
|
|
504
|
+
if (isCancel(lintCommand)) {
|
|
505
|
+
process.exit(130);
|
|
506
|
+
}
|
|
507
|
+
const buildCommand = await text({
|
|
508
|
+
message: "Build/check command",
|
|
509
|
+
placeholder: "npm run build",
|
|
510
|
+
defaultValue: defaults?.buildCommand,
|
|
511
|
+
});
|
|
512
|
+
if (isCancel(buildCommand)) {
|
|
513
|
+
process.exit(130);
|
|
514
|
+
}
|
|
515
|
+
const stackSummary = await text({
|
|
516
|
+
message: "Stack summary",
|
|
517
|
+
placeholder: "Next.js, TypeScript, Tailwind CSS, Vitest",
|
|
518
|
+
defaultValue: defaults?.stackSummary,
|
|
519
|
+
});
|
|
520
|
+
if (isCancel(stackSummary)) {
|
|
521
|
+
process.exit(130);
|
|
522
|
+
}
|
|
523
|
+
return {
|
|
524
|
+
projectName,
|
|
525
|
+
projectDescription,
|
|
526
|
+
issueTracker,
|
|
527
|
+
designSystemPath,
|
|
528
|
+
briefsPath,
|
|
529
|
+
testCommand,
|
|
530
|
+
lintCommand,
|
|
531
|
+
buildCommand,
|
|
532
|
+
stackSummary,
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
async function buildInitTemplateContent(file, preset, personalization) {
|
|
536
|
+
const content = await buildTemplateContent(file, preset);
|
|
537
|
+
return personalizeTemplateContent(file, content, personalization);
|
|
538
|
+
}
|
|
143
539
|
async function installTemplates(targetArg, options) {
|
|
144
540
|
const targetDir = path.resolve(process.cwd(), targetArg || ".");
|
|
145
|
-
const
|
|
541
|
+
const allTemplateFiles = await getTemplateFiles();
|
|
542
|
+
const files = options.files ??
|
|
543
|
+
(options.templateSet || options.aiTools
|
|
544
|
+
? getSelectedTemplateFiles(options.templateSet ?? "full", options.aiTools ?? [], allTemplateFiles)
|
|
545
|
+
: allTemplateFiles);
|
|
146
546
|
const preset = resolvePreset(options.preset);
|
|
147
547
|
const created = [];
|
|
148
548
|
const skipped = [];
|
|
149
549
|
if (!options.dryRun) {
|
|
150
550
|
await mkdir(targetDir, { recursive: true });
|
|
151
551
|
}
|
|
552
|
+
if (options.writeConfig) {
|
|
553
|
+
const destination = path.join(targetDir, configFileName);
|
|
554
|
+
const destinationExists = await exists(destination);
|
|
555
|
+
if (destinationExists && !options.force) {
|
|
556
|
+
skipped.push(configFileName);
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
created.push(configFileName);
|
|
560
|
+
if (!options.dryRun) {
|
|
561
|
+
await writeFile(destination, serializeConfig(getResolvedConfig(options)));
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
152
565
|
for (const file of files) {
|
|
153
|
-
const source = path.join(templatesDir, file);
|
|
154
566
|
const destination = path.join(targetDir, file);
|
|
155
567
|
const destinationExists = await exists(destination);
|
|
156
568
|
if (destinationExists && !options.force) {
|
|
@@ -161,11 +573,11 @@ async function installTemplates(targetArg, options) {
|
|
|
161
573
|
if (!options.dryRun) {
|
|
162
574
|
await mkdir(path.dirname(destination), { recursive: true });
|
|
163
575
|
if (preset && file === "AGENTS.md") {
|
|
164
|
-
const content = await
|
|
165
|
-
await writeFile(destination, wrapManagedBlock(file,
|
|
576
|
+
const content = await buildInitTemplateContent(file, preset, options.personalization);
|
|
577
|
+
await writeFile(destination, wrapManagedBlock(file, content));
|
|
166
578
|
}
|
|
167
579
|
else {
|
|
168
|
-
const content = await
|
|
580
|
+
const content = await buildInitTemplateContent(file, preset, options.personalization);
|
|
169
581
|
await writeFile(destination, wrapManagedBlock(file, content));
|
|
170
582
|
}
|
|
171
583
|
}
|
|
@@ -301,42 +713,97 @@ function printUpdateResult(result, dryRun = false) {
|
|
|
301
713
|
}
|
|
302
714
|
async function resolveInteractiveTarget(target, options) {
|
|
303
715
|
const providedPreset = resolvePreset(options.preset);
|
|
304
|
-
|
|
716
|
+
const shouldPrompt = !options.yes && (options.interactive || process.stdin.isTTY);
|
|
717
|
+
if (!shouldPrompt) {
|
|
305
718
|
return target;
|
|
306
719
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
720
|
+
intro("Welcome to AgentKit");
|
|
721
|
+
let resolvedTarget = target;
|
|
722
|
+
if (!target || target === ".") {
|
|
723
|
+
const targetResponse = await text({
|
|
724
|
+
message: "Where should AgentKit install files?",
|
|
725
|
+
placeholder: ".",
|
|
726
|
+
defaultValue: ".",
|
|
727
|
+
});
|
|
728
|
+
if (isCancel(targetResponse)) {
|
|
729
|
+
process.exit(130);
|
|
730
|
+
}
|
|
731
|
+
resolvedTarget = targetResponse || ".";
|
|
732
|
+
}
|
|
733
|
+
if (!providedPreset) {
|
|
734
|
+
const projectTypeResponse = await select({
|
|
735
|
+
message: "What type of project is this?",
|
|
736
|
+
initialValue: "generic",
|
|
737
|
+
options: [
|
|
738
|
+
{ label: "Generic TypeScript project", value: "generic" },
|
|
739
|
+
{ label: "Next.js app", value: "next" },
|
|
740
|
+
{ label: "SvelteKit app", value: "sveltekit" },
|
|
741
|
+
{ label: "Express API", value: "express" },
|
|
742
|
+
{ label: "Convex app", value: "convex" },
|
|
743
|
+
{ label: "Fullstack app", value: "fullstack" },
|
|
744
|
+
],
|
|
745
|
+
});
|
|
746
|
+
if (isCancel(projectTypeResponse)) {
|
|
747
|
+
process.exit(130);
|
|
748
|
+
}
|
|
749
|
+
options.preset = resolveProjectPreset(projectTypeResponse);
|
|
314
750
|
}
|
|
315
|
-
const
|
|
316
|
-
message: "
|
|
317
|
-
|
|
751
|
+
const aiToolResponse = await multiselect({
|
|
752
|
+
message: "Which AI tools do you use?",
|
|
753
|
+
initialValues: options.aiTools ?? ["codex", "cursor", "claude"],
|
|
754
|
+
options: [
|
|
755
|
+
{ label: "Codex", value: "codex" },
|
|
756
|
+
{ label: "Cursor", value: "cursor" },
|
|
757
|
+
{ label: "Claude Code", value: "claude" },
|
|
758
|
+
{ label: "GitHub Copilot", value: "copilot" },
|
|
759
|
+
],
|
|
318
760
|
});
|
|
319
|
-
if (isCancel(
|
|
761
|
+
if (isCancel(aiToolResponse)) {
|
|
320
762
|
process.exit(130);
|
|
321
763
|
}
|
|
322
|
-
const
|
|
323
|
-
message: "Which
|
|
324
|
-
initialValue:
|
|
764
|
+
const templateSetResponse = await select({
|
|
765
|
+
message: "Which template set do you want?",
|
|
766
|
+
initialValue: options.templateSet ?? "standard",
|
|
325
767
|
options: [
|
|
326
|
-
{ label: "
|
|
327
|
-
{ label: "
|
|
328
|
-
{ label: "
|
|
329
|
-
{ label: "Express", value: "express" },
|
|
330
|
-
{ label: "Convex", value: "convex" },
|
|
331
|
-
{ label: "Fullstack", value: "fullstack" },
|
|
768
|
+
{ label: "Minimal", value: "minimal" },
|
|
769
|
+
{ label: "Standard", value: "standard" },
|
|
770
|
+
{ label: "Full", value: "full" },
|
|
332
771
|
],
|
|
333
772
|
});
|
|
334
|
-
if (isCancel(
|
|
773
|
+
if (isCancel(templateSetResponse)) {
|
|
335
774
|
process.exit(130);
|
|
336
775
|
}
|
|
337
|
-
|
|
338
|
-
options.
|
|
339
|
-
|
|
776
|
+
const templateFiles = await getTemplateFiles();
|
|
777
|
+
options.templateSet = templateSetResponse;
|
|
778
|
+
options.aiTools = aiToolResponse;
|
|
779
|
+
options.files = getSelectedTemplateFiles(templateSetResponse, aiToolResponse, templateFiles);
|
|
780
|
+
if (!options.force) {
|
|
781
|
+
const preset = resolvePreset(options.preset);
|
|
782
|
+
const installFiles = preset ? [...options.files, "STACK.md"] : options.files;
|
|
783
|
+
const targetDir = path.resolve(process.cwd(), resolvedTarget || ".");
|
|
784
|
+
const existingFiles = [];
|
|
785
|
+
for (const file of installFiles) {
|
|
786
|
+
if (await exists(path.join(targetDir, file))) {
|
|
787
|
+
existingFiles.push(file);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
if (existingFiles.length > 0) {
|
|
791
|
+
const conflictResponse = await select({
|
|
792
|
+
message: `Existing files found: ${existingFiles.join(", ")}. How should AgentKit handle conflicts?`,
|
|
793
|
+
initialValue: "skip",
|
|
794
|
+
options: [
|
|
795
|
+
{ label: "Skip existing files", value: "skip" },
|
|
796
|
+
{ label: "Overwrite existing files", value: "overwrite" },
|
|
797
|
+
],
|
|
798
|
+
});
|
|
799
|
+
if (isCancel(conflictResponse)) {
|
|
800
|
+
process.exit(130);
|
|
801
|
+
}
|
|
802
|
+
options.force = conflictResponse === "overwrite";
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
options.personalization = await promptForPersonalization(options.personalization);
|
|
806
|
+
return resolvedTarget || ".";
|
|
340
807
|
}
|
|
341
808
|
async function main() {
|
|
342
809
|
if (process.argv.slice(2).includes("--list")) {
|
|
@@ -365,7 +832,7 @@ Examples:
|
|
|
365
832
|
agentkit init
|
|
366
833
|
agentkit update
|
|
367
834
|
agentkit init --preset next
|
|
368
|
-
agentkit init ./my-project --dry-run
|
|
835
|
+
agentkit init ./my-project --yes --dry-run
|
|
369
836
|
agentkit --list-presets
|
|
370
837
|
agentkit --list`);
|
|
371
838
|
program
|
|
@@ -376,8 +843,10 @@ Examples:
|
|
|
376
843
|
.option("--dry-run", "print planned changes without writing files")
|
|
377
844
|
.option("-i, --interactive", "prompt for install options")
|
|
378
845
|
.option("-y, --yes", "accept defaults for non-interactive runs")
|
|
846
|
+
.option("--write-config", "write resolved install defaults to agentkit.config.json")
|
|
379
847
|
.option("--preset <name>", `install stack-specific guidance (${formatPresetList()})`)
|
|
380
848
|
.action(async (target, options) => {
|
|
849
|
+
await applyInitConfig(options, await loadConfigForTarget(target));
|
|
381
850
|
const resolvedTarget = await resolveInteractiveTarget(target, options);
|
|
382
851
|
const result = await installTemplates(resolvedTarget, options);
|
|
383
852
|
printInstallResult(result, Boolean(options.dryRun));
|
|
@@ -389,13 +858,16 @@ Examples:
|
|
|
389
858
|
.option("--dry-run", "print planned changes without writing files")
|
|
390
859
|
.option("--preset <name>", `update stack-specific guidance (${formatPresetList()})`)
|
|
391
860
|
.action(async (target, options) => {
|
|
861
|
+
applyUpdateConfig(options, await loadConfigForTarget(target));
|
|
392
862
|
const result = await updateTemplates(target, options);
|
|
393
863
|
printUpdateResult(result, Boolean(options.dryRun));
|
|
394
864
|
});
|
|
395
865
|
await program.parseAsync(process.argv);
|
|
396
866
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
867
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === __filename) {
|
|
868
|
+
main().catch((error) => {
|
|
869
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
870
|
+
console.error(message);
|
|
871
|
+
process.exitCode = 1;
|
|
872
|
+
});
|
|
873
|
+
}
|