thomas-agentkit 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +71 -5
- package/dist/cli.js +449 -39
- package/package.json +18 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Howard Thomas
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
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:
|
|
@@ -32,7 +42,19 @@ Overwrite existing files:
|
|
|
32
42
|
npx thomas-agentkit init --force
|
|
33
43
|
```
|
|
34
44
|
|
|
35
|
-
|
|
45
|
+
Update AgentKit-managed template sections:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npx thomas-agentkit update
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Preview updates without writing files:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npx thomas-agentkit update --dry-run
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Explicitly request the interactive flow:
|
|
36
58
|
|
|
37
59
|
```bash
|
|
38
60
|
npx thomas-agentkit init --interactive
|
|
@@ -75,6 +97,18 @@ When a preset is selected, AgentKit also installs `STACK.md` and adds a note in
|
|
|
75
97
|
|
|
76
98
|
Existing files are skipped by default so local edits are preserved. Use `--force` when you intentionally want to refresh files from the bundled package version.
|
|
77
99
|
|
|
100
|
+
New installs wrap generated template content in AgentKit managed block markers:
|
|
101
|
+
|
|
102
|
+
```html
|
|
103
|
+
<!-- agentkit:start agents -->
|
|
104
|
+
Generated content
|
|
105
|
+
<!-- agentkit:end agents -->
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`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.
|
|
109
|
+
|
|
110
|
+
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.
|
|
111
|
+
|
|
78
112
|
## Presets
|
|
79
113
|
|
|
80
114
|
Presets add stack-specific guidance without scaffolding framework app files.
|
|
@@ -89,6 +123,7 @@ Presets add stack-specific guidance without scaffolding framework app files.
|
|
|
89
123
|
|
|
90
124
|
```text
|
|
91
125
|
agentkit init [target] [--force] [--dry-run] [--interactive] [--yes] [--preset <name>]
|
|
126
|
+
agentkit update [target] [--dry-run] [--preset <name>]
|
|
92
127
|
agentkit --list
|
|
93
128
|
agentkit --list-presets
|
|
94
129
|
agentkit --help
|
|
@@ -99,25 +134,56 @@ Options:
|
|
|
99
134
|
|
|
100
135
|
- `--force`: overwrite existing files
|
|
101
136
|
- `--dry-run`: print planned changes without writing files
|
|
102
|
-
- `-i, --interactive`: prompt for install options
|
|
103
|
-
- `-y, --yes`: accept defaults
|
|
137
|
+
- `-i, --interactive`: explicitly prompt for install options
|
|
138
|
+
- `-y, --yes`: accept defaults without prompts
|
|
104
139
|
- `--preset <name>`: install stack-specific guidance (`next`, `sveltekit`, `express`, `convex`, `fullstack`)
|
|
105
140
|
- `--list`: list bundled template files
|
|
106
141
|
- `--list-presets`: list available presets
|
|
107
142
|
- `-h, --help`: show help
|
|
108
143
|
- `-v, --version`: show package version
|
|
109
144
|
|
|
145
|
+
For `agentkit update`, `--preset <name>` refreshes preset-specific managed content, including `STACK.md`.
|
|
146
|
+
|
|
110
147
|
## Local Development
|
|
111
148
|
|
|
112
149
|
```bash
|
|
113
150
|
npm install
|
|
114
|
-
npm run dev -- init ./tmp-demo --dry-run
|
|
151
|
+
npm run dev -- init ./tmp-demo --yes --dry-run
|
|
115
152
|
npm run build
|
|
116
153
|
npm test
|
|
117
154
|
```
|
|
118
155
|
|
|
119
156
|
The npm package is `thomas-agentkit`. The installed CLI command is `agentkit`, backed by the compiled TypeScript entrypoint at `dist/cli.js`.
|
|
120
157
|
|
|
158
|
+
## Release
|
|
159
|
+
|
|
160
|
+
Before publishing a new package version:
|
|
161
|
+
|
|
162
|
+
1. Update the version in `package.json` intentionally.
|
|
163
|
+
2. Run the full test suite:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
npm test
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
3. Run the package build step:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
npm run prepack
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
4. Inspect the publish contents:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
npm pack --dry-run
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
5. Publish to npm:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
npm publish
|
|
185
|
+
```
|
|
186
|
+
|
|
121
187
|
## Philosophy
|
|
122
188
|
|
|
123
189
|
AgentKit should stay:
|
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
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
|
-
import { access,
|
|
5
|
+
import { access, mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -11,6 +11,8 @@ 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"];
|
|
14
16
|
const presetLabels = {
|
|
15
17
|
next: "Next.js",
|
|
16
18
|
sveltekit: "SvelteKit",
|
|
@@ -18,6 +20,17 @@ const presetLabels = {
|
|
|
18
20
|
convex: "Convex",
|
|
19
21
|
fullstack: "Fullstack",
|
|
20
22
|
};
|
|
23
|
+
const aiToolFiles = {
|
|
24
|
+
codex: ["AGENTS.md"],
|
|
25
|
+
cursor: [".cursor/rules/agentkit.md"],
|
|
26
|
+
claude: ["CLAUDE.md"],
|
|
27
|
+
copilot: [".github/copilot-instructions.md"],
|
|
28
|
+
};
|
|
29
|
+
const templateSetFiles = {
|
|
30
|
+
minimal: ["AGENTS.md"],
|
|
31
|
+
standard: ["AGENTS.md", "CODE-QUALITY.md", "DESIGN-SYSTEM.md", "WORKFLOWS.md"],
|
|
32
|
+
full: [],
|
|
33
|
+
};
|
|
21
34
|
const stackGuidance = {
|
|
22
35
|
next: `# Stack Guidance
|
|
23
36
|
|
|
@@ -101,6 +114,12 @@ async function getTemplateFiles(dir = templatesDir, base = templatesDir) {
|
|
|
101
114
|
function isPresetName(value) {
|
|
102
115
|
return validPresets.includes(value);
|
|
103
116
|
}
|
|
117
|
+
function isProjectTypeName(value) {
|
|
118
|
+
return validProjectTypes.includes(value);
|
|
119
|
+
}
|
|
120
|
+
function isAiToolName(value) {
|
|
121
|
+
return validAiTools.includes(value);
|
|
122
|
+
}
|
|
104
123
|
function formatPresetList() {
|
|
105
124
|
return validPresets.join(", ");
|
|
106
125
|
}
|
|
@@ -114,9 +133,54 @@ function resolvePreset(preset) {
|
|
|
114
133
|
}
|
|
115
134
|
return normalizedPreset;
|
|
116
135
|
}
|
|
136
|
+
export function resolveProjectPreset(projectType) {
|
|
137
|
+
if (!projectType) {
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
const normalizedProjectType = projectType.toLowerCase();
|
|
141
|
+
if (!isProjectTypeName(normalizedProjectType)) {
|
|
142
|
+
throw new Error(`Unknown project type "${projectType}". Valid project types: ${validProjectTypes.join(", ")}.`);
|
|
143
|
+
}
|
|
144
|
+
return normalizedProjectType === "generic" ? undefined : normalizedProjectType;
|
|
145
|
+
}
|
|
146
|
+
export function getFilesForAiTools(aiTools) {
|
|
147
|
+
const files = new Set();
|
|
148
|
+
for (const aiTool of aiTools) {
|
|
149
|
+
if (!isAiToolName(aiTool)) {
|
|
150
|
+
throw new Error(`Unknown AI tool "${aiTool}". Valid AI tools: ${validAiTools.join(", ")}.`);
|
|
151
|
+
}
|
|
152
|
+
for (const file of aiToolFiles[aiTool]) {
|
|
153
|
+
files.add(file);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return [...files].sort();
|
|
157
|
+
}
|
|
158
|
+
export function getFilesForTemplateSet(templateSet, allTemplateFiles) {
|
|
159
|
+
if (templateSet === "full") {
|
|
160
|
+
return [...allTemplateFiles].sort();
|
|
161
|
+
}
|
|
162
|
+
return templateSetFiles[templateSet].filter((file) => allTemplateFiles.includes(file)).sort();
|
|
163
|
+
}
|
|
164
|
+
export function getSelectedTemplateFiles(templateSet, aiTools, allTemplateFiles) {
|
|
165
|
+
const files = new Set([...getFilesForTemplateSet(templateSet, allTemplateFiles), ...getFilesForAiTools(aiTools)]);
|
|
166
|
+
return [...files].filter((file) => allTemplateFiles.includes(file)).sort();
|
|
167
|
+
}
|
|
117
168
|
function getStackGuidance(preset) {
|
|
118
169
|
return preset === "fullstack" ? fullstackGuidance : stackGuidance[preset];
|
|
119
170
|
}
|
|
171
|
+
function getTemplateId(file) {
|
|
172
|
+
return file
|
|
173
|
+
.replace(/\.[^/.]+$/, "")
|
|
174
|
+
.replace(/^\./, "")
|
|
175
|
+
.split("/")
|
|
176
|
+
.filter(Boolean)
|
|
177
|
+
.join("-")
|
|
178
|
+
.toLowerCase();
|
|
179
|
+
}
|
|
180
|
+
function wrapManagedBlock(file, content) {
|
|
181
|
+
const id = getTemplateId(file);
|
|
182
|
+
return `<!-- agentkit:start ${id} -->\n${content.trimEnd()}\n<!-- agentkit:end ${id} -->\n`;
|
|
183
|
+
}
|
|
120
184
|
function addStackReference(file, content, preset) {
|
|
121
185
|
if (file !== "AGENTS.md" || !preset) {
|
|
122
186
|
return content;
|
|
@@ -127,9 +191,190 @@ function addStackReference(file, content, preset) {
|
|
|
127
191
|
}
|
|
128
192
|
return `${content.trimEnd()}\n${stackNote}`;
|
|
129
193
|
}
|
|
194
|
+
function cleanPersonalizationValue(value) {
|
|
195
|
+
const trimmed = value?.trim();
|
|
196
|
+
return trimmed ? trimmed : undefined;
|
|
197
|
+
}
|
|
198
|
+
function replaceIfProvided(content, placeholder, value) {
|
|
199
|
+
const replacement = cleanPersonalizationValue(value);
|
|
200
|
+
return replacement ? content.replaceAll(placeholder, replacement) : content;
|
|
201
|
+
}
|
|
202
|
+
function getProvidedCommands(values) {
|
|
203
|
+
return [values.testCommand, values.lintCommand, values.buildCommand]
|
|
204
|
+
.map(cleanPersonalizationValue)
|
|
205
|
+
.filter((command) => Boolean(command));
|
|
206
|
+
}
|
|
207
|
+
function commandDescription(command, kind) {
|
|
208
|
+
const descriptions = {
|
|
209
|
+
test: "Run tests",
|
|
210
|
+
lint: "Run lint checks",
|
|
211
|
+
build: "Build or check the project",
|
|
212
|
+
};
|
|
213
|
+
return descriptions[kind] ?? command;
|
|
214
|
+
}
|
|
215
|
+
function replaceCommandBlock(content, commands) {
|
|
216
|
+
if (commands.length === 0) {
|
|
217
|
+
return content;
|
|
218
|
+
}
|
|
219
|
+
const commandBlock = ["```bash", ...commands, "```"].join("\n");
|
|
220
|
+
return content
|
|
221
|
+
.replace(/```bash\nnpm install\nnpm test\nnpm run build\nnpm run lint\n```/, commandBlock)
|
|
222
|
+
.replace(/```bash\nnpm test\nnpm run lint\nnpm run build\n```/, commandBlock);
|
|
223
|
+
}
|
|
224
|
+
function replaceAgentCommandTable(content, values) {
|
|
225
|
+
const rows = [
|
|
226
|
+
cleanPersonalizationValue(values.testCommand)
|
|
227
|
+
? `| \`${cleanPersonalizationValue(values.testCommand)}\` | ${commandDescription(values.testCommand ?? "", "test")} |`
|
|
228
|
+
: undefined,
|
|
229
|
+
cleanPersonalizationValue(values.lintCommand)
|
|
230
|
+
? `| \`${cleanPersonalizationValue(values.lintCommand)}\` | ${commandDescription(values.lintCommand ?? "", "lint")} |`
|
|
231
|
+
: undefined,
|
|
232
|
+
cleanPersonalizationValue(values.buildCommand)
|
|
233
|
+
? `| \`${cleanPersonalizationValue(values.buildCommand)}\` | ${commandDescription(values.buildCommand ?? "", "build")} |`
|
|
234
|
+
: undefined,
|
|
235
|
+
].filter((row) => Boolean(row));
|
|
236
|
+
if (rows.length === 0) {
|
|
237
|
+
return content;
|
|
238
|
+
}
|
|
239
|
+
const nextTable = ["| Command | Description |", "| --- | --- |", ...rows].join("\n");
|
|
240
|
+
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);
|
|
241
|
+
}
|
|
242
|
+
function replaceStackSummary(content, stackSummary) {
|
|
243
|
+
const summary = cleanPersonalizationValue(stackSummary);
|
|
244
|
+
if (!summary) {
|
|
245
|
+
return content;
|
|
246
|
+
}
|
|
247
|
+
const stackItems = summary
|
|
248
|
+
.split(/\r?\n|,/)
|
|
249
|
+
.map((item) => item.trim())
|
|
250
|
+
.filter(Boolean)
|
|
251
|
+
.map((item) => `- ${item}`)
|
|
252
|
+
.join("\n");
|
|
253
|
+
return content.replace(/- \[Primary framework\]\n- \[Language\/runtime\]\n- \[Backend\/data layer\]\n- \[Styling system\]\n- \[Test tools\]\n- \[Lint\/format tools\]/, stackItems);
|
|
254
|
+
}
|
|
255
|
+
export function personalizeTemplateContent(file, content, values) {
|
|
256
|
+
if (!values) {
|
|
257
|
+
return content;
|
|
258
|
+
}
|
|
259
|
+
if (file === "PRD-TEMPLATE.md" ||
|
|
260
|
+
file === "IMPLEMENTATION-BRIEF-TEMPLATE.md" ||
|
|
261
|
+
file === ".github/pull_request_template.md") {
|
|
262
|
+
return content;
|
|
263
|
+
}
|
|
264
|
+
let personalized = content;
|
|
265
|
+
if (file === "AGENTS.md" || file === "DESIGN-SYSTEM.md") {
|
|
266
|
+
personalized = replaceIfProvided(personalized, "[Project Name]", values.projectName);
|
|
267
|
+
}
|
|
268
|
+
if (file === "AGENTS.md") {
|
|
269
|
+
personalized = replaceIfProvided(personalized, "[short project description]", values.projectDescription);
|
|
270
|
+
personalized = replaceIfProvided(personalized, "[issue tracker, e.g. Linear or GitHub Issues]", values.issueTracker);
|
|
271
|
+
personalized = replaceIfProvided(personalized, "[design system path, e.g. docs/design-system.md]", values.designSystemPath);
|
|
272
|
+
personalized = replaceIfProvided(personalized, "[design system path]", values.designSystemPath);
|
|
273
|
+
personalized = replaceIfProvided(personalized, "[briefs path, e.g. docs/briefs]", values.briefsPath);
|
|
274
|
+
personalized = replaceIfProvided(personalized, "[test command, e.g. npm test]", values.testCommand);
|
|
275
|
+
personalized = replaceIfProvided(personalized, "[lint command, e.g. npm run lint]", values.lintCommand);
|
|
276
|
+
personalized = replaceIfProvided(personalized, "[build/check command, e.g. npm run build]", values.buildCommand);
|
|
277
|
+
personalized = replaceAgentCommandTable(personalized, values);
|
|
278
|
+
personalized = replaceStackSummary(personalized, values.stackSummary);
|
|
279
|
+
}
|
|
280
|
+
if (file === "CLAUDE.md" || file === "CODE-QUALITY.md") {
|
|
281
|
+
personalized = replaceCommandBlock(personalized, getProvidedCommands(values));
|
|
282
|
+
}
|
|
283
|
+
return personalized;
|
|
284
|
+
}
|
|
285
|
+
async function promptForPersonalization() {
|
|
286
|
+
const shouldPersonalize = await confirm({
|
|
287
|
+
message: "Personalize template placeholders?",
|
|
288
|
+
initialValue: false,
|
|
289
|
+
});
|
|
290
|
+
if (isCancel(shouldPersonalize)) {
|
|
291
|
+
process.exit(130);
|
|
292
|
+
}
|
|
293
|
+
if (!shouldPersonalize) {
|
|
294
|
+
return undefined;
|
|
295
|
+
}
|
|
296
|
+
const projectName = await text({
|
|
297
|
+
message: "Project name",
|
|
298
|
+
placeholder: "[Project Name]",
|
|
299
|
+
});
|
|
300
|
+
if (isCancel(projectName)) {
|
|
301
|
+
process.exit(130);
|
|
302
|
+
}
|
|
303
|
+
const projectDescription = await text({
|
|
304
|
+
message: "Short project description",
|
|
305
|
+
placeholder: "[short project description]",
|
|
306
|
+
});
|
|
307
|
+
if (isCancel(projectDescription)) {
|
|
308
|
+
process.exit(130);
|
|
309
|
+
}
|
|
310
|
+
const issueTracker = await text({
|
|
311
|
+
message: "Issue tracker name",
|
|
312
|
+
placeholder: "Linear or GitHub Issues",
|
|
313
|
+
});
|
|
314
|
+
if (isCancel(issueTracker)) {
|
|
315
|
+
process.exit(130);
|
|
316
|
+
}
|
|
317
|
+
const designSystemPath = await text({
|
|
318
|
+
message: "Design system path",
|
|
319
|
+
placeholder: "docs/design-system.md",
|
|
320
|
+
});
|
|
321
|
+
if (isCancel(designSystemPath)) {
|
|
322
|
+
process.exit(130);
|
|
323
|
+
}
|
|
324
|
+
const briefsPath = await text({
|
|
325
|
+
message: "Briefs path",
|
|
326
|
+
placeholder: "docs/briefs",
|
|
327
|
+
});
|
|
328
|
+
if (isCancel(briefsPath)) {
|
|
329
|
+
process.exit(130);
|
|
330
|
+
}
|
|
331
|
+
const testCommand = await text({
|
|
332
|
+
message: "Test command",
|
|
333
|
+
placeholder: "npm test",
|
|
334
|
+
});
|
|
335
|
+
if (isCancel(testCommand)) {
|
|
336
|
+
process.exit(130);
|
|
337
|
+
}
|
|
338
|
+
const lintCommand = await text({
|
|
339
|
+
message: "Lint command",
|
|
340
|
+
placeholder: "npm run lint",
|
|
341
|
+
});
|
|
342
|
+
if (isCancel(lintCommand)) {
|
|
343
|
+
process.exit(130);
|
|
344
|
+
}
|
|
345
|
+
const buildCommand = await text({
|
|
346
|
+
message: "Build/check command",
|
|
347
|
+
placeholder: "npm run build",
|
|
348
|
+
});
|
|
349
|
+
if (isCancel(buildCommand)) {
|
|
350
|
+
process.exit(130);
|
|
351
|
+
}
|
|
352
|
+
const stackSummary = await text({
|
|
353
|
+
message: "Stack summary",
|
|
354
|
+
placeholder: "Next.js, TypeScript, Tailwind CSS, Vitest",
|
|
355
|
+
});
|
|
356
|
+
if (isCancel(stackSummary)) {
|
|
357
|
+
process.exit(130);
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
projectName,
|
|
361
|
+
projectDescription,
|
|
362
|
+
issueTracker,
|
|
363
|
+
designSystemPath,
|
|
364
|
+
briefsPath,
|
|
365
|
+
testCommand,
|
|
366
|
+
lintCommand,
|
|
367
|
+
buildCommand,
|
|
368
|
+
stackSummary,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
async function buildInitTemplateContent(file, preset, personalization) {
|
|
372
|
+
const content = await buildTemplateContent(file, preset);
|
|
373
|
+
return personalizeTemplateContent(file, content, personalization);
|
|
374
|
+
}
|
|
130
375
|
async function installTemplates(targetArg, options) {
|
|
131
376
|
const targetDir = path.resolve(process.cwd(), targetArg || ".");
|
|
132
|
-
const files = await getTemplateFiles();
|
|
377
|
+
const files = options.files ?? (await getTemplateFiles());
|
|
133
378
|
const preset = resolvePreset(options.preset);
|
|
134
379
|
const created = [];
|
|
135
380
|
const skipped = [];
|
|
@@ -137,7 +382,6 @@ async function installTemplates(targetArg, options) {
|
|
|
137
382
|
await mkdir(targetDir, { recursive: true });
|
|
138
383
|
}
|
|
139
384
|
for (const file of files) {
|
|
140
|
-
const source = path.join(templatesDir, file);
|
|
141
385
|
const destination = path.join(targetDir, file);
|
|
142
386
|
const destinationExists = await exists(destination);
|
|
143
387
|
if (destinationExists && !options.force) {
|
|
@@ -148,11 +392,12 @@ async function installTemplates(targetArg, options) {
|
|
|
148
392
|
if (!options.dryRun) {
|
|
149
393
|
await mkdir(path.dirname(destination), { recursive: true });
|
|
150
394
|
if (preset && file === "AGENTS.md") {
|
|
151
|
-
const content = await
|
|
152
|
-
await writeFile(destination,
|
|
395
|
+
const content = await buildInitTemplateContent(file, preset, options.personalization);
|
|
396
|
+
await writeFile(destination, wrapManagedBlock(file, content));
|
|
153
397
|
}
|
|
154
398
|
else {
|
|
155
|
-
await
|
|
399
|
+
const content = await buildInitTemplateContent(file, preset, options.personalization);
|
|
400
|
+
await writeFile(destination, wrapManagedBlock(file, content));
|
|
156
401
|
}
|
|
157
402
|
}
|
|
158
403
|
}
|
|
@@ -166,12 +411,84 @@ async function installTemplates(targetArg, options) {
|
|
|
166
411
|
else {
|
|
167
412
|
created.push(stackFile);
|
|
168
413
|
if (!options.dryRun) {
|
|
169
|
-
await writeFile(destination, getStackGuidance(preset));
|
|
414
|
+
await writeFile(destination, wrapManagedBlock(stackFile, getStackGuidance(preset)));
|
|
170
415
|
}
|
|
171
416
|
}
|
|
172
417
|
}
|
|
173
418
|
return { targetDir, created, skipped };
|
|
174
419
|
}
|
|
420
|
+
function replaceManagedBlock(file, existingContent, nextContent) {
|
|
421
|
+
const id = getTemplateId(file);
|
|
422
|
+
const startMarker = `<!-- agentkit:start ${id} -->`;
|
|
423
|
+
const endMarker = `<!-- agentkit:end ${id} -->`;
|
|
424
|
+
const startIndex = existingContent.indexOf(startMarker);
|
|
425
|
+
const endIndex = existingContent.indexOf(endMarker);
|
|
426
|
+
if (startIndex === -1 && endIndex === -1) {
|
|
427
|
+
return undefined;
|
|
428
|
+
}
|
|
429
|
+
if (startIndex === -1 || endIndex === -1 || endIndex < startIndex) {
|
|
430
|
+
throw new Error(`Malformed managed block in ${file}.`);
|
|
431
|
+
}
|
|
432
|
+
const afterEndIndex = endIndex + endMarker.length;
|
|
433
|
+
const replacement = wrapManagedBlock(file, nextContent).trimEnd();
|
|
434
|
+
return `${existingContent.slice(0, startIndex)}${replacement}${existingContent.slice(afterEndIndex)}`;
|
|
435
|
+
}
|
|
436
|
+
async function buildTemplateContent(file, preset) {
|
|
437
|
+
if (file === "STACK.md") {
|
|
438
|
+
if (!preset) {
|
|
439
|
+
throw new Error("STACK.md requires a preset.");
|
|
440
|
+
}
|
|
441
|
+
return getStackGuidance(preset);
|
|
442
|
+
}
|
|
443
|
+
const source = path.join(templatesDir, file);
|
|
444
|
+
const content = await readFile(source, "utf8");
|
|
445
|
+
return addStackReference(file, content, preset);
|
|
446
|
+
}
|
|
447
|
+
async function updateTemplates(targetArg, options) {
|
|
448
|
+
const targetDir = path.resolve(process.cwd(), targetArg || ".");
|
|
449
|
+
const preset = resolvePreset(options.preset);
|
|
450
|
+
const files = preset ? [...(await getTemplateFiles()), "STACK.md"] : await getTemplateFiles();
|
|
451
|
+
const created = [];
|
|
452
|
+
const updated = [];
|
|
453
|
+
const skipped = [];
|
|
454
|
+
const malformed = [];
|
|
455
|
+
const unchanged = [];
|
|
456
|
+
for (const file of files) {
|
|
457
|
+
const destination = path.join(targetDir, file);
|
|
458
|
+
const nextContent = await buildTemplateContent(file, preset);
|
|
459
|
+
const destinationExists = await exists(destination);
|
|
460
|
+
if (!destinationExists) {
|
|
461
|
+
created.push(file);
|
|
462
|
+
if (!options.dryRun) {
|
|
463
|
+
await mkdir(path.dirname(destination), { recursive: true });
|
|
464
|
+
await writeFile(destination, wrapManagedBlock(file, nextContent));
|
|
465
|
+
}
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
const existingContent = await readFile(destination, "utf8");
|
|
469
|
+
let updatedContent;
|
|
470
|
+
try {
|
|
471
|
+
updatedContent = replaceManagedBlock(file, existingContent, nextContent);
|
|
472
|
+
}
|
|
473
|
+
catch {
|
|
474
|
+
malformed.push(file);
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
if (updatedContent === undefined) {
|
|
478
|
+
skipped.push(file);
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
if (updatedContent === existingContent) {
|
|
482
|
+
unchanged.push(file);
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
updated.push(file);
|
|
486
|
+
if (!options.dryRun) {
|
|
487
|
+
await writeFile(destination, updatedContent);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return { targetDir, created, updated, skipped, malformed, unchanged };
|
|
491
|
+
}
|
|
175
492
|
function printInstallResult(result, dryRun = false) {
|
|
176
493
|
const action = dryRun ? "Would install" : "Installed";
|
|
177
494
|
console.log(`${action} AgentKit files in ${result.targetDir}`);
|
|
@@ -186,44 +503,124 @@ function printInstallResult(result, dryRun = false) {
|
|
|
186
503
|
console.log("No bundled templates found.");
|
|
187
504
|
}
|
|
188
505
|
}
|
|
506
|
+
function printUpdateResult(result, dryRun = false) {
|
|
507
|
+
const action = dryRun ? "Would update" : "Updated";
|
|
508
|
+
console.log(`${action} AgentKit files in ${result.targetDir}`);
|
|
509
|
+
if (result.created.length > 0) {
|
|
510
|
+
console.log(`${dryRun ? "Would create" : "Created"}: ${result.created.join(", ")}`);
|
|
511
|
+
}
|
|
512
|
+
if (result.updated.length > 0) {
|
|
513
|
+
console.log(`${dryRun ? "Would update" : "Updated"}: ${result.updated.join(", ")}`);
|
|
514
|
+
}
|
|
515
|
+
if (result.unchanged.length > 0) {
|
|
516
|
+
console.log(`Already current: ${result.unchanged.join(", ")}`);
|
|
517
|
+
}
|
|
518
|
+
if (result.skipped.length > 0) {
|
|
519
|
+
console.log(`Skipped unmanaged: ${result.skipped.join(", ")}`);
|
|
520
|
+
console.log("Add AgentKit managed block markers before updating these files.");
|
|
521
|
+
}
|
|
522
|
+
if (result.malformed.length > 0) {
|
|
523
|
+
console.log(`Skipped malformed: ${result.malformed.join(", ")}`);
|
|
524
|
+
console.log("Fix AgentKit managed block markers before updating these files.");
|
|
525
|
+
}
|
|
526
|
+
if (result.created.length === 0 &&
|
|
527
|
+
result.updated.length === 0 &&
|
|
528
|
+
result.skipped.length === 0 &&
|
|
529
|
+
result.malformed.length === 0) {
|
|
530
|
+
console.log("All managed AgentKit files are current.");
|
|
531
|
+
}
|
|
532
|
+
}
|
|
189
533
|
async function resolveInteractiveTarget(target, options) {
|
|
190
534
|
const providedPreset = resolvePreset(options.preset);
|
|
191
|
-
|
|
535
|
+
const shouldPrompt = !options.yes && (options.interactive || process.stdin.isTTY);
|
|
536
|
+
if (!shouldPrompt) {
|
|
192
537
|
return target;
|
|
193
538
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
539
|
+
intro("Welcome to AgentKit");
|
|
540
|
+
let resolvedTarget = target;
|
|
541
|
+
if (!target || target === ".") {
|
|
542
|
+
const targetResponse = await text({
|
|
543
|
+
message: "Where should AgentKit install files?",
|
|
544
|
+
placeholder: ".",
|
|
545
|
+
defaultValue: ".",
|
|
546
|
+
});
|
|
547
|
+
if (isCancel(targetResponse)) {
|
|
548
|
+
process.exit(130);
|
|
549
|
+
}
|
|
550
|
+
resolvedTarget = targetResponse || ".";
|
|
201
551
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
552
|
+
if (!providedPreset) {
|
|
553
|
+
const projectTypeResponse = await select({
|
|
554
|
+
message: "What type of project is this?",
|
|
555
|
+
initialValue: "generic",
|
|
556
|
+
options: [
|
|
557
|
+
{ label: "Generic TypeScript project", value: "generic" },
|
|
558
|
+
{ label: "Next.js app", value: "next" },
|
|
559
|
+
{ label: "SvelteKit app", value: "sveltekit" },
|
|
560
|
+
{ label: "Express API", value: "express" },
|
|
561
|
+
{ label: "Convex app", value: "convex" },
|
|
562
|
+
{ label: "Fullstack app", value: "fullstack" },
|
|
563
|
+
],
|
|
564
|
+
});
|
|
565
|
+
if (isCancel(projectTypeResponse)) {
|
|
566
|
+
process.exit(130);
|
|
567
|
+
}
|
|
568
|
+
options.preset = resolveProjectPreset(projectTypeResponse);
|
|
569
|
+
}
|
|
570
|
+
const aiToolResponse = await multiselect({
|
|
571
|
+
message: "Which AI tools do you use?",
|
|
572
|
+
initialValues: ["codex", "cursor", "claude"],
|
|
573
|
+
options: [
|
|
574
|
+
{ label: "Codex", value: "codex" },
|
|
575
|
+
{ label: "Cursor", value: "cursor" },
|
|
576
|
+
{ label: "Claude Code", value: "claude" },
|
|
577
|
+
{ label: "GitHub Copilot", value: "copilot" },
|
|
578
|
+
],
|
|
205
579
|
});
|
|
206
|
-
if (isCancel(
|
|
580
|
+
if (isCancel(aiToolResponse)) {
|
|
207
581
|
process.exit(130);
|
|
208
582
|
}
|
|
209
|
-
const
|
|
210
|
-
message: "Which
|
|
211
|
-
initialValue:
|
|
583
|
+
const templateSetResponse = await select({
|
|
584
|
+
message: "Which template set do you want?",
|
|
585
|
+
initialValue: "standard",
|
|
212
586
|
options: [
|
|
213
|
-
{ label: "
|
|
214
|
-
{ label: "
|
|
215
|
-
{ label: "
|
|
216
|
-
{ label: "Express", value: "express" },
|
|
217
|
-
{ label: "Convex", value: "convex" },
|
|
218
|
-
{ label: "Fullstack", value: "fullstack" },
|
|
587
|
+
{ label: "Minimal", value: "minimal" },
|
|
588
|
+
{ label: "Standard", value: "standard" },
|
|
589
|
+
{ label: "Full", value: "full" },
|
|
219
590
|
],
|
|
220
591
|
});
|
|
221
|
-
if (isCancel(
|
|
592
|
+
if (isCancel(templateSetResponse)) {
|
|
222
593
|
process.exit(130);
|
|
223
594
|
}
|
|
224
|
-
|
|
225
|
-
options.
|
|
226
|
-
|
|
595
|
+
const templateFiles = await getTemplateFiles();
|
|
596
|
+
options.files = getSelectedTemplateFiles(templateSetResponse, aiToolResponse, templateFiles);
|
|
597
|
+
if (!options.force) {
|
|
598
|
+
const preset = resolvePreset(options.preset);
|
|
599
|
+
const installFiles = preset ? [...options.files, "STACK.md"] : options.files;
|
|
600
|
+
const targetDir = path.resolve(process.cwd(), resolvedTarget || ".");
|
|
601
|
+
const existingFiles = [];
|
|
602
|
+
for (const file of installFiles) {
|
|
603
|
+
if (await exists(path.join(targetDir, file))) {
|
|
604
|
+
existingFiles.push(file);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
if (existingFiles.length > 0) {
|
|
608
|
+
const conflictResponse = await select({
|
|
609
|
+
message: `Existing files found: ${existingFiles.join(", ")}. How should AgentKit handle conflicts?`,
|
|
610
|
+
initialValue: "skip",
|
|
611
|
+
options: [
|
|
612
|
+
{ label: "Skip existing files", value: "skip" },
|
|
613
|
+
{ label: "Overwrite existing files", value: "overwrite" },
|
|
614
|
+
],
|
|
615
|
+
});
|
|
616
|
+
if (isCancel(conflictResponse)) {
|
|
617
|
+
process.exit(130);
|
|
618
|
+
}
|
|
619
|
+
options.force = conflictResponse === "overwrite";
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
options.personalization = await promptForPersonalization();
|
|
623
|
+
return resolvedTarget || ".";
|
|
227
624
|
}
|
|
228
625
|
async function main() {
|
|
229
626
|
if (process.argv.slice(2).includes("--list")) {
|
|
@@ -250,8 +647,9 @@ async function main() {
|
|
|
250
647
|
|
|
251
648
|
Examples:
|
|
252
649
|
agentkit init
|
|
650
|
+
agentkit update
|
|
253
651
|
agentkit init --preset next
|
|
254
|
-
agentkit init ./my-project --dry-run
|
|
652
|
+
agentkit init ./my-project --yes --dry-run
|
|
255
653
|
agentkit --list-presets
|
|
256
654
|
agentkit --list`);
|
|
257
655
|
program
|
|
@@ -268,10 +666,22 @@ Examples:
|
|
|
268
666
|
const result = await installTemplates(resolvedTarget, options);
|
|
269
667
|
printInstallResult(result, Boolean(options.dryRun));
|
|
270
668
|
});
|
|
669
|
+
program
|
|
670
|
+
.command("update")
|
|
671
|
+
.description("update AgentKit managed template blocks in a project")
|
|
672
|
+
.argument("[target]", "target project directory", ".")
|
|
673
|
+
.option("--dry-run", "print planned changes without writing files")
|
|
674
|
+
.option("--preset <name>", `update stack-specific guidance (${formatPresetList()})`)
|
|
675
|
+
.action(async (target, options) => {
|
|
676
|
+
const result = await updateTemplates(target, options);
|
|
677
|
+
printUpdateResult(result, Boolean(options.dryRun));
|
|
678
|
+
});
|
|
271
679
|
await program.parseAsync(process.argv);
|
|
272
680
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
681
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === __filename) {
|
|
682
|
+
main().catch((error) => {
|
|
683
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
684
|
+
console.error(message);
|
|
685
|
+
process.exitCode = 1;
|
|
686
|
+
});
|
|
687
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thomas-agentkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Install AI-agent-ready development templates into a project.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/howie1329/Howard-AgentKit.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/howie1329/Howard-AgentKit#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/howie1329/Howard-AgentKit/issues"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"agentkit",
|
|
16
|
+
"ai",
|
|
17
|
+
"agents",
|
|
18
|
+
"codex",
|
|
19
|
+
"cli",
|
|
20
|
+
"scaffolding",
|
|
21
|
+
"templates"
|
|
22
|
+
],
|
|
6
23
|
"type": "module",
|
|
7
24
|
"bin": {
|
|
8
25
|
"agentkit": "./dist/cli.js"
|