thomas-agentkit 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +82 -1
  3. package/dist/cli.js +261 -3
  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
@@ -32,18 +32,42 @@ Overwrite existing files:
32
32
  npx thomas-agentkit init --force
33
33
  ```
34
34
 
35
+ Update AgentKit-managed template sections:
36
+
37
+ ```bash
38
+ npx thomas-agentkit update
39
+ ```
40
+
41
+ Preview updates without writing files:
42
+
43
+ ```bash
44
+ npx thomas-agentkit update --dry-run
45
+ ```
46
+
35
47
  Use the optional interactive flow:
36
48
 
37
49
  ```bash
38
50
  npx thomas-agentkit init --interactive
39
51
  ```
40
52
 
53
+ Install stack-specific agent guidance:
54
+
55
+ ```bash
56
+ npx thomas-agentkit init --preset next
57
+ ```
58
+
41
59
  List bundled templates:
42
60
 
43
61
  ```bash
44
62
  npx thomas-agentkit --list
45
63
  ```
46
64
 
65
+ List available presets:
66
+
67
+ ```bash
68
+ npx thomas-agentkit --list-presets
69
+ ```
70
+
47
71
  ## Installed Files
48
72
 
49
73
  AgentKit copies these bundled files into the target project:
@@ -59,13 +83,37 @@ AgentKit copies these bundled files into the target project:
59
83
  - `.github/copilot-instructions.md`
60
84
  - `.github/pull_request_template.md`
61
85
 
86
+ When a preset is selected, AgentKit also installs `STACK.md` and adds a note in `AGENTS.md` telling agents to read it before changing stack-specific code.
87
+
62
88
  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.
63
89
 
90
+ New installs wrap generated template content in AgentKit managed block markers:
91
+
92
+ ```html
93
+ <!-- agentkit:start agents -->
94
+ Generated content
95
+ <!-- agentkit:end agents -->
96
+ ```
97
+
98
+ `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
+
100
+ ## Presets
101
+
102
+ Presets add stack-specific guidance without scaffolding framework app files.
103
+
104
+ - `next`
105
+ - `sveltekit`
106
+ - `express`
107
+ - `convex`
108
+ - `fullstack` (`Next.js` + `Convex`)
109
+
64
110
  ## CLI Reference
65
111
 
66
112
  ```text
67
- agentkit init [target] [--force] [--dry-run] [--interactive] [--yes]
113
+ agentkit init [target] [--force] [--dry-run] [--interactive] [--yes] [--preset <name>]
114
+ agentkit update [target] [--dry-run] [--preset <name>]
68
115
  agentkit --list
116
+ agentkit --list-presets
69
117
  agentkit --help
70
118
  agentkit --version
71
119
  ```
@@ -76,10 +124,14 @@ Options:
76
124
  - `--dry-run`: print planned changes without writing files
77
125
  - `-i, --interactive`: prompt for install options
78
126
  - `-y, --yes`: accept defaults for non-interactive runs
127
+ - `--preset <name>`: install stack-specific guidance (`next`, `sveltekit`, `express`, `convex`, `fullstack`)
79
128
  - `--list`: list bundled template files
129
+ - `--list-presets`: list available presets
80
130
  - `-h, --help`: show help
81
131
  - `-v, --version`: show package version
82
132
 
133
+ For `agentkit update`, `--preset <name>` refreshes preset-specific managed content, including `STACK.md`.
134
+
83
135
  ## Local Development
84
136
 
85
137
  ```bash
@@ -91,6 +143,35 @@ npm test
91
143
 
92
144
  The npm package is `thomas-agentkit`. The installed CLI command is `agentkit`, backed by the compiled TypeScript entrypoint at `dist/cli.js`.
93
145
 
146
+ ## Release
147
+
148
+ Before publishing a new package version:
149
+
150
+ 1. Update the version in `package.json` intentionally.
151
+ 2. Run the full test suite:
152
+
153
+ ```bash
154
+ npm test
155
+ ```
156
+
157
+ 3. Run the package build step:
158
+
159
+ ```bash
160
+ npm run prepack
161
+ ```
162
+
163
+ 4. Inspect the publish contents:
164
+
165
+ ```bash
166
+ npm pack --dry-run
167
+ ```
168
+
169
+ 5. Publish to npm:
170
+
171
+ ```bash
172
+ npm publish
173
+ ```
174
+
94
175
  ## Philosophy
95
176
 
96
177
  AgentKit should stay:
package/dist/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { confirm, isCancel, text } from "@clack/prompts";
2
+ import { confirm, isCancel, 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 } 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);
@@ -10,6 +10,63 @@ const __dirname = path.dirname(__filename);
10
10
  const packageRoot = path.resolve(__dirname, "..");
11
11
  const templatesDir = path.join(packageRoot, "templates");
12
12
  const packageJsonPath = path.join(packageRoot, "package.json");
13
+ const validPresets = ["next", "sveltekit", "express", "convex", "fullstack"];
14
+ const presetLabels = {
15
+ next: "Next.js",
16
+ sveltekit: "SvelteKit",
17
+ express: "Express",
18
+ convex: "Convex",
19
+ fullstack: "Fullstack",
20
+ };
21
+ const stackGuidance = {
22
+ next: `# Stack Guidance
23
+
24
+ ## Next.js
25
+
26
+ - Follow the app's existing routing model before adding new routes or layouts.
27
+ - Keep server and client component boundaries explicit.
28
+ - Prefer server components for data loading unless interactivity requires a client component.
29
+ - Keep mutations in server actions, route handlers, or existing API layers based on local patterns.
30
+ - Use established styling primitives and design tokens before adding new UI conventions.
31
+ - Validate external input at route, action, and API boundaries.
32
+ - Run the project's Next.js build or typecheck before handoff when touching routing, rendering, or data loading.
33
+ `,
34
+ sveltekit: `# Stack Guidance
35
+
36
+ ## SvelteKit
37
+
38
+ - Follow the existing route, load, action, and server module patterns before adding new files.
39
+ - Keep browser-only code out of server load functions and server modules.
40
+ - Use SvelteKit form actions and load functions where they fit the workflow.
41
+ - Validate external input at action, endpoint, and server boundary entrypoints.
42
+ - Reuse existing stores, components, and styling conventions before creating new ones.
43
+ - Run the project's SvelteKit check or build before handoff when touching routes, rendering, or data loading.
44
+ `,
45
+ express: `# Stack Guidance
46
+
47
+ ## Express
48
+
49
+ - Keep route handlers small and move repeated business logic only when duplication is real.
50
+ - Validate request params, query strings, and bodies at the route boundary.
51
+ - Return explicit status codes and predictable response shapes.
52
+ - Use the project's existing middleware order and error handling pattern.
53
+ - Avoid adding global middleware or dependencies for narrow endpoint changes.
54
+ - Add focused tests for route behavior, validation failures, and error paths.
55
+ `,
56
+ convex: `# Stack Guidance
57
+
58
+ ## Convex
59
+
60
+ - Follow generated Convex types and local function patterns before editing schema or functions.
61
+ - Keep queries, mutations, and actions focused on one clear responsibility.
62
+ - Validate arguments with Convex validators at public function boundaries.
63
+ - Preserve explicit authorization checks for user-scoped data.
64
+ - Prefer indexes and schema changes that match real query needs.
65
+ - Run Convex codegen or the project's Convex validation command after schema or function changes.
66
+ `,
67
+ };
68
+ const fullstackGuidance = `${stackGuidance.next}
69
+ ${stackGuidance.convex.replace("# Stack Guidance\n\n", "")}`;
13
70
  async function exists(filePath) {
14
71
  try {
15
72
  await access(filePath, fsConstants.F_OK);
@@ -41,9 +98,52 @@ async function getTemplateFiles(dir = templatesDir, base = templatesDir) {
41
98
  }));
42
99
  return files.flat().sort();
43
100
  }
101
+ function isPresetName(value) {
102
+ return validPresets.includes(value);
103
+ }
104
+ function formatPresetList() {
105
+ return validPresets.join(", ");
106
+ }
107
+ function resolvePreset(preset) {
108
+ if (!preset) {
109
+ return undefined;
110
+ }
111
+ const normalizedPreset = preset.toLowerCase();
112
+ if (!isPresetName(normalizedPreset)) {
113
+ throw new Error(`Unknown preset "${preset}". Valid presets: ${formatPresetList()}.`);
114
+ }
115
+ return normalizedPreset;
116
+ }
117
+ function getStackGuidance(preset) {
118
+ return preset === "fullstack" ? fullstackGuidance : stackGuidance[preset];
119
+ }
120
+ function getTemplateId(file) {
121
+ return file
122
+ .replace(/\.[^/.]+$/, "")
123
+ .replace(/^\./, "")
124
+ .split("/")
125
+ .filter(Boolean)
126
+ .join("-")
127
+ .toLowerCase();
128
+ }
129
+ function wrapManagedBlock(file, content) {
130
+ const id = getTemplateId(file);
131
+ return `<!-- agentkit:start ${id} -->\n${content.trimEnd()}\n<!-- agentkit:end ${id} -->\n`;
132
+ }
133
+ function addStackReference(file, content, preset) {
134
+ if (file !== "AGENTS.md" || !preset) {
135
+ return content;
136
+ }
137
+ const stackNote = `\nPreset: ${presetLabels[preset]}. Agents must read \`STACK.md\` before changing stack-specific code.\n`;
138
+ if (content.includes("## Guidelines")) {
139
+ return content.replace("\n## Guidelines", `${stackNote}\n## Guidelines`);
140
+ }
141
+ return `${content.trimEnd()}\n${stackNote}`;
142
+ }
44
143
  async function installTemplates(targetArg, options) {
45
144
  const targetDir = path.resolve(process.cwd(), targetArg || ".");
46
145
  const files = await getTemplateFiles();
146
+ const preset = resolvePreset(options.preset);
47
147
  const created = [];
48
148
  const skipped = [];
49
149
  if (!options.dryRun) {
@@ -60,11 +160,104 @@ async function installTemplates(targetArg, options) {
60
160
  created.push(file);
61
161
  if (!options.dryRun) {
62
162
  await mkdir(path.dirname(destination), { recursive: true });
63
- await copyFile(source, destination);
163
+ if (preset && file === "AGENTS.md") {
164
+ const content = await readFile(source, "utf8");
165
+ await writeFile(destination, wrapManagedBlock(file, addStackReference(file, content, preset)));
166
+ }
167
+ else {
168
+ const content = await readFile(source, "utf8");
169
+ await writeFile(destination, wrapManagedBlock(file, content));
170
+ }
171
+ }
172
+ }
173
+ if (preset) {
174
+ const stackFile = "STACK.md";
175
+ const destination = path.join(targetDir, stackFile);
176
+ const destinationExists = await exists(destination);
177
+ if (destinationExists && !options.force) {
178
+ skipped.push(stackFile);
179
+ }
180
+ else {
181
+ created.push(stackFile);
182
+ if (!options.dryRun) {
183
+ await writeFile(destination, wrapManagedBlock(stackFile, getStackGuidance(preset)));
184
+ }
64
185
  }
65
186
  }
66
187
  return { targetDir, created, skipped };
67
188
  }
189
+ function replaceManagedBlock(file, existingContent, nextContent) {
190
+ const id = getTemplateId(file);
191
+ const startMarker = `<!-- agentkit:start ${id} -->`;
192
+ const endMarker = `<!-- agentkit:end ${id} -->`;
193
+ const startIndex = existingContent.indexOf(startMarker);
194
+ const endIndex = existingContent.indexOf(endMarker);
195
+ if (startIndex === -1 && endIndex === -1) {
196
+ return undefined;
197
+ }
198
+ if (startIndex === -1 || endIndex === -1 || endIndex < startIndex) {
199
+ throw new Error(`Malformed managed block in ${file}.`);
200
+ }
201
+ const afterEndIndex = endIndex + endMarker.length;
202
+ const replacement = wrapManagedBlock(file, nextContent).trimEnd();
203
+ return `${existingContent.slice(0, startIndex)}${replacement}${existingContent.slice(afterEndIndex)}`;
204
+ }
205
+ async function buildTemplateContent(file, preset) {
206
+ if (file === "STACK.md") {
207
+ if (!preset) {
208
+ throw new Error("STACK.md requires a preset.");
209
+ }
210
+ return getStackGuidance(preset);
211
+ }
212
+ const source = path.join(templatesDir, file);
213
+ const content = await readFile(source, "utf8");
214
+ return addStackReference(file, content, preset);
215
+ }
216
+ async function updateTemplates(targetArg, options) {
217
+ const targetDir = path.resolve(process.cwd(), targetArg || ".");
218
+ const preset = resolvePreset(options.preset);
219
+ const files = preset ? [...(await getTemplateFiles()), "STACK.md"] : await getTemplateFiles();
220
+ const created = [];
221
+ const updated = [];
222
+ const skipped = [];
223
+ const malformed = [];
224
+ const unchanged = [];
225
+ for (const file of files) {
226
+ const destination = path.join(targetDir, file);
227
+ const nextContent = await buildTemplateContent(file, preset);
228
+ const destinationExists = await exists(destination);
229
+ if (!destinationExists) {
230
+ created.push(file);
231
+ if (!options.dryRun) {
232
+ await mkdir(path.dirname(destination), { recursive: true });
233
+ await writeFile(destination, wrapManagedBlock(file, nextContent));
234
+ }
235
+ continue;
236
+ }
237
+ const existingContent = await readFile(destination, "utf8");
238
+ let updatedContent;
239
+ try {
240
+ updatedContent = replaceManagedBlock(file, existingContent, nextContent);
241
+ }
242
+ catch {
243
+ malformed.push(file);
244
+ continue;
245
+ }
246
+ if (updatedContent === undefined) {
247
+ skipped.push(file);
248
+ continue;
249
+ }
250
+ if (updatedContent === existingContent) {
251
+ unchanged.push(file);
252
+ continue;
253
+ }
254
+ updated.push(file);
255
+ if (!options.dryRun) {
256
+ await writeFile(destination, updatedContent);
257
+ }
258
+ }
259
+ return { targetDir, created, updated, skipped, malformed, unchanged };
260
+ }
68
261
  function printInstallResult(result, dryRun = false) {
69
262
  const action = dryRun ? "Would install" : "Installed";
70
263
  console.log(`${action} AgentKit files in ${result.targetDir}`);
@@ -79,7 +272,35 @@ function printInstallResult(result, dryRun = false) {
79
272
  console.log("No bundled templates found.");
80
273
  }
81
274
  }
275
+ function printUpdateResult(result, dryRun = false) {
276
+ const action = dryRun ? "Would update" : "Updated";
277
+ console.log(`${action} AgentKit files in ${result.targetDir}`);
278
+ if (result.created.length > 0) {
279
+ console.log(`${dryRun ? "Would create" : "Created"}: ${result.created.join(", ")}`);
280
+ }
281
+ if (result.updated.length > 0) {
282
+ console.log(`${dryRun ? "Would update" : "Updated"}: ${result.updated.join(", ")}`);
283
+ }
284
+ if (result.unchanged.length > 0) {
285
+ console.log(`Already current: ${result.unchanged.join(", ")}`);
286
+ }
287
+ if (result.skipped.length > 0) {
288
+ console.log(`Skipped unmanaged: ${result.skipped.join(", ")}`);
289
+ console.log("Add AgentKit managed block markers before updating these files.");
290
+ }
291
+ if (result.malformed.length > 0) {
292
+ console.log(`Skipped malformed: ${result.malformed.join(", ")}`);
293
+ console.log("Fix AgentKit managed block markers before updating these files.");
294
+ }
295
+ if (result.created.length === 0 &&
296
+ result.updated.length === 0 &&
297
+ result.skipped.length === 0 &&
298
+ result.malformed.length === 0) {
299
+ console.log("All managed AgentKit files are current.");
300
+ }
301
+ }
82
302
  async function resolveInteractiveTarget(target, options) {
303
+ const providedPreset = resolvePreset(options.preset);
83
304
  if (!options.interactive) {
84
305
  return target;
85
306
  }
@@ -98,6 +319,22 @@ async function resolveInteractiveTarget(target, options) {
98
319
  if (isCancel(forceResponse)) {
99
320
  process.exit(130);
100
321
  }
322
+ const presetResponse = await select({
323
+ message: "Which preset should AgentKit use?",
324
+ initialValue: providedPreset || "generic",
325
+ options: [
326
+ { label: "Generic", value: "generic" },
327
+ { label: "Next.js", value: "next" },
328
+ { label: "SvelteKit", value: "sveltekit" },
329
+ { label: "Express", value: "express" },
330
+ { label: "Convex", value: "convex" },
331
+ { label: "Fullstack", value: "fullstack" },
332
+ ],
333
+ });
334
+ if (isCancel(presetResponse)) {
335
+ process.exit(130);
336
+ }
337
+ options.preset = presetResponse === "generic" ? undefined : presetResponse;
101
338
  options.force = forceResponse;
102
339
  return targetResponse || ".";
103
340
  }
@@ -109,17 +346,27 @@ async function main() {
109
346
  }
110
347
  return;
111
348
  }
349
+ if (process.argv.slice(2).includes("--list-presets")) {
350
+ for (const preset of validPresets) {
351
+ console.log(preset);
352
+ }
353
+ return;
354
+ }
112
355
  const program = new Command();
113
356
  program
114
357
  .name("agentkit")
115
358
  .description("Bootstrap AI-agent-ready repository docs and workflow templates.")
116
359
  .version(await readPackageVersion(), "-v, --version")
117
360
  .option("--list", "list bundled template files")
361
+ .option("--list-presets", "list available presets")
118
362
  .addHelpText("after", `
119
363
 
120
364
  Examples:
121
365
  agentkit init
366
+ agentkit update
367
+ agentkit init --preset next
122
368
  agentkit init ./my-project --dry-run
369
+ agentkit --list-presets
123
370
  agentkit --list`);
124
371
  program
125
372
  .command("init")
@@ -129,11 +376,22 @@ Examples:
129
376
  .option("--dry-run", "print planned changes without writing files")
130
377
  .option("-i, --interactive", "prompt for install options")
131
378
  .option("-y, --yes", "accept defaults for non-interactive runs")
379
+ .option("--preset <name>", `install stack-specific guidance (${formatPresetList()})`)
132
380
  .action(async (target, options) => {
133
381
  const resolvedTarget = await resolveInteractiveTarget(target, options);
134
382
  const result = await installTemplates(resolvedTarget, options);
135
383
  printInstallResult(result, Boolean(options.dryRun));
136
384
  });
385
+ program
386
+ .command("update")
387
+ .description("update AgentKit managed template blocks in a project")
388
+ .argument("[target]", "target project directory", ".")
389
+ .option("--dry-run", "print planned changes without writing files")
390
+ .option("--preset <name>", `update stack-specific guidance (${formatPresetList()})`)
391
+ .action(async (target, options) => {
392
+ const result = await updateTemplates(target, options);
393
+ printUpdateResult(result, Boolean(options.dryRun));
394
+ });
137
395
  await program.parseAsync(process.argv);
138
396
  }
139
397
  main().catch((error) => {
package/package.json CHANGED
@@ -1,8 +1,25 @@
1
1
  {
2
2
  "name": "thomas-agentkit",
3
- "version": "0.1.0",
3
+ "version": "0.3.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"