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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +71 -5
  3. package/dist/cli.js +449 -39
  4. 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
- Use the optional interactive flow:
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 for non-interactive runs
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, copyFile, mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
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 readFile(source, "utf8");
152
- await writeFile(destination, addStackReference(file, content, preset));
395
+ const content = await buildInitTemplateContent(file, preset, options.personalization);
396
+ await writeFile(destination, wrapManagedBlock(file, content));
153
397
  }
154
398
  else {
155
- await copyFile(source, destination);
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
- if (!options.interactive) {
535
+ const shouldPrompt = !options.yes && (options.interactive || process.stdin.isTTY);
536
+ if (!shouldPrompt) {
192
537
  return target;
193
538
  }
194
- const targetResponse = await text({
195
- message: "Where should AgentKit install templates?",
196
- placeholder: target || ".",
197
- defaultValue: target || ".",
198
- });
199
- if (isCancel(targetResponse)) {
200
- process.exit(130);
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
- const forceResponse = await confirm({
203
- message: "Overwrite existing files?",
204
- initialValue: Boolean(options.force),
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(forceResponse)) {
580
+ if (isCancel(aiToolResponse)) {
207
581
  process.exit(130);
208
582
  }
209
- const presetResponse = await select({
210
- message: "Which preset should AgentKit use?",
211
- initialValue: providedPreset || "generic",
583
+ const templateSetResponse = await select({
584
+ message: "Which template set do you want?",
585
+ initialValue: "standard",
212
586
  options: [
213
- { label: "Generic", value: "generic" },
214
- { label: "Next.js", value: "next" },
215
- { label: "SvelteKit", value: "sveltekit" },
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(presetResponse)) {
592
+ if (isCancel(templateSetResponse)) {
222
593
  process.exit(130);
223
594
  }
224
- options.preset = presetResponse === "generic" ? undefined : presetResponse;
225
- options.force = forceResponse;
226
- return targetResponse || ".";
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
- main().catch((error) => {
274
- const message = error instanceof Error ? error.message : String(error);
275
- console.error(message);
276
- process.exitCode = 1;
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.2.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"