thomas-agentkit 0.4.0 → 0.5.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/README.md CHANGED
@@ -66,6 +66,38 @@ Install stack-specific agent guidance:
66
66
  npx thomas-agentkit init --preset next
67
67
  ```
68
68
 
69
+ Standardize install defaults with `agentkit.config.json`:
70
+
71
+ ```json
72
+ {
73
+ "preset": "next",
74
+ "templateSet": "standard",
75
+ "designSystem": "linear",
76
+ "aiTools": ["codex", "cursor", "claude"],
77
+ "personalization": {
78
+ "projectName": "Acme CRM",
79
+ "projectDescription": "a customer operations dashboard",
80
+ "issueTracker": "Linear",
81
+ "designSystemPath": "docs/design-system.md",
82
+ "briefsPath": "docs/briefs",
83
+ "testCommand": "pnpm test",
84
+ "lintCommand": "pnpm lint",
85
+ "buildCommand": "pnpm build",
86
+ "stackSummary": "Next.js, TypeScript, PostgreSQL"
87
+ }
88
+ }
89
+ ```
90
+
91
+ 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.
92
+
93
+ Create a config file from resolved install choices:
94
+
95
+ ```bash
96
+ npx thomas-agentkit init --write-config
97
+ ```
98
+
99
+ `--write-config` writes `agentkit.config.json` in the target directory. Existing config files are skipped by default; use `--force` to overwrite one intentionally.
100
+
69
101
  List bundled templates:
70
102
 
71
103
  ```bash
@@ -78,6 +110,12 @@ List available presets:
78
110
  npx thomas-agentkit --list-presets
79
111
  ```
80
112
 
113
+ List bundled design systems (source variants for `DESIGN-SYSTEM.md`):
114
+
115
+ ```bash
116
+ npx thomas-agentkit --list-design-systems
117
+ ```
118
+
81
119
  ## Installed Files
82
120
 
83
121
  AgentKit copies these bundled files into the target project:
@@ -95,6 +133,8 @@ AgentKit copies these bundled files into the target project:
95
133
 
96
134
  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.
97
135
 
136
+ Bundled design system guidance is stored under `templates/design-systems/` in this repository (for example `linear.md`). The CLI installs the chosen variant into `DESIGN-SYSTEM.md` in the target project; variant paths are not separate install targets.
137
+
98
138
  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.
99
139
 
100
140
  New installs wrap generated template content in AgentKit managed block markers:
@@ -109,6 +149,8 @@ Generated content
109
149
 
110
150
  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
151
 
152
+ `agentkit.config.json` can set `preset`, `templateSet`, `designSystem`, `aiTools`, and `personalization` defaults. `templateSet` may be `minimal`, `standard`, or `full`; `designSystem` selects which bundled design-system variant fills `DESIGN-SYSTEM.md` (currently `linear`); `aiTools` may include `codex`, `cursor`, `claude`, and `copilot`. `agentkit update` uses config `preset` and `designSystem` and continues to update all managed bundled templates.
153
+
112
154
  ## Presets
113
155
 
114
156
  Presets add stack-specific guidance without scaffolding framework app files.
@@ -122,10 +164,11 @@ Presets add stack-specific guidance without scaffolding framework app files.
122
164
  ## CLI Reference
123
165
 
124
166
  ```text
125
- agentkit init [target] [--force] [--dry-run] [--interactive] [--yes] [--preset <name>]
126
- agentkit update [target] [--dry-run] [--preset <name>]
167
+ agentkit init [target] [--force] [--dry-run] [--interactive] [--yes] [--write-config] [--preset <name>] [--design-system <name>]
168
+ agentkit update [target] [--dry-run] [--preset <name>] [--design-system <name>]
127
169
  agentkit --list
128
170
  agentkit --list-presets
171
+ agentkit --list-design-systems
129
172
  agentkit --help
130
173
  agentkit --version
131
174
  ```
@@ -136,13 +179,16 @@ Options:
136
179
  - `--dry-run`: print planned changes without writing files
137
180
  - `-i, --interactive`: explicitly prompt for install options
138
181
  - `-y, --yes`: accept defaults without prompts
182
+ - `--write-config`: write resolved install defaults to `agentkit.config.json`
139
183
  - `--preset <name>`: install stack-specific guidance (`next`, `sveltekit`, `express`, `convex`, `fullstack`)
184
+ - `--design-system <name>`: design system variant for `DESIGN-SYSTEM.md` (`linear`)
140
185
  - `--list`: list bundled template files
141
186
  - `--list-presets`: list available presets
187
+ - `--list-design-systems`: list available design systems
142
188
  - `-h, --help`: show help
143
189
  - `-v, --version`: show package version
144
190
 
145
- For `agentkit update`, `--preset <name>` refreshes preset-specific managed content, including `STACK.md`.
191
+ For `agentkit update`, `--preset <name>` refreshes preset-specific managed content, including `STACK.md`. `--design-system <name>` refreshes the managed `DESIGN-SYSTEM.md` body from the matching bundled variant.
146
192
 
147
193
  ## Local Development
148
194
 
package/dist/cli.js CHANGED
@@ -13,6 +13,24 @@ const packageJsonPath = path.join(packageRoot, "package.json");
13
13
  const validPresets = ["next", "sveltekit", "express", "convex", "fullstack"];
14
14
  const validProjectTypes = ["generic", ...validPresets];
15
15
  const validAiTools = ["codex", "cursor", "claude", "copilot"];
16
+ const validTemplateSets = ["minimal", "standard", "full"];
17
+ const validDesignSystems = ["linear"];
18
+ const designSystemLabels = {
19
+ linear: "Linear-inspired",
20
+ };
21
+ const configFileName = "agentkit.config.json";
22
+ const configKeys = ["preset", "templateSet", "aiTools", "designSystem", "personalization"];
23
+ const personalizationKeys = [
24
+ "projectName",
25
+ "projectDescription",
26
+ "issueTracker",
27
+ "designSystemPath",
28
+ "briefsPath",
29
+ "testCommand",
30
+ "lintCommand",
31
+ "buildCommand",
32
+ "stackSummary",
33
+ ];
16
34
  const presetLabels = {
17
35
  next: "Next.js",
18
36
  sveltekit: "SvelteKit",
@@ -93,23 +111,35 @@ async function readPackageVersion() {
93
111
  const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
94
112
  return packageJson.version ?? "0.0.0";
95
113
  }
96
- async function getTemplateFiles(dir = templatesDir, base = templatesDir) {
114
+ async function collectInstallableTemplatePaths(dir, base) {
97
115
  const dirStat = await stat(dir);
98
116
  if (!dirStat.isDirectory()) {
99
117
  throw new Error(`Bundled templates directory not found: ${templatesDir}`);
100
118
  }
101
119
  const entries = await readdir(dir, { withFileTypes: true });
102
- const files = await Promise.all(entries.map(async (entry) => {
120
+ const discovered = [];
121
+ for (const entry of entries) {
103
122
  const absolutePath = path.join(dir, entry.name);
104
123
  if (entry.isDirectory()) {
105
- return getTemplateFiles(absolutePath, base);
124
+ if (dir === templatesDir && entry.name === "design-systems") {
125
+ continue;
126
+ }
127
+ discovered.push(...(await collectInstallableTemplatePaths(absolutePath, base)));
128
+ continue;
106
129
  }
107
130
  if (!entry.isFile()) {
108
- return [];
131
+ continue;
109
132
  }
110
- return [path.relative(base, absolutePath).split(path.sep).join("/")];
111
- }));
112
- return files.flat().sort();
133
+ discovered.push(path.relative(base, absolutePath).split(path.sep).join("/"));
134
+ }
135
+ return discovered;
136
+ }
137
+ async function getTemplateFiles() {
138
+ const discovered = await collectInstallableTemplatePaths(templatesDir, templatesDir);
139
+ const withDesignSystem = discovered.includes("DESIGN-SYSTEM.md")
140
+ ? discovered
141
+ : [...discovered, "DESIGN-SYSTEM.md"];
142
+ return withDesignSystem.sort();
113
143
  }
114
144
  function isPresetName(value) {
115
145
  return validPresets.includes(value);
@@ -120,9 +150,35 @@ function isProjectTypeName(value) {
120
150
  function isAiToolName(value) {
121
151
  return validAiTools.includes(value);
122
152
  }
153
+ function isTemplateSetName(value) {
154
+ return validTemplateSets.includes(value);
155
+ }
156
+ function isDesignSystemName(value) {
157
+ return validDesignSystems.includes(value);
158
+ }
123
159
  function formatPresetList() {
124
160
  return validPresets.join(", ");
125
161
  }
162
+ function formatTemplateSetList() {
163
+ return validTemplateSets.join(", ");
164
+ }
165
+ function formatDesignSystemList() {
166
+ return validDesignSystems.join(", ");
167
+ }
168
+ function resolveDesignSystem(name) {
169
+ const normalized = name.toLowerCase();
170
+ if (!isDesignSystemName(normalized)) {
171
+ throw new Error(`Unknown design system "${name}". Valid design systems: ${formatDesignSystemList()}.`);
172
+ }
173
+ return normalized;
174
+ }
175
+ function effectiveDesignSystem(name) {
176
+ const trimmed = name?.trim();
177
+ if (!trimmed) {
178
+ return "linear";
179
+ }
180
+ return resolveDesignSystem(trimmed);
181
+ }
126
182
  function resolvePreset(preset) {
127
183
  if (!preset) {
128
184
  return undefined;
@@ -133,6 +189,123 @@ function resolvePreset(preset) {
133
189
  }
134
190
  return normalizedPreset;
135
191
  }
192
+ function resolveTemplateSet(templateSet) {
193
+ if (!templateSet) {
194
+ return undefined;
195
+ }
196
+ const normalizedTemplateSet = templateSet.toLowerCase();
197
+ if (!isTemplateSetName(normalizedTemplateSet)) {
198
+ throw new Error(`Unknown template set "${templateSet}". Valid template sets: ${formatTemplateSetList()}.`);
199
+ }
200
+ return normalizedTemplateSet;
201
+ }
202
+ function assertPlainObject(value, name) {
203
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
204
+ throw new Error(`${name} must be an object.`);
205
+ }
206
+ }
207
+ function assertKnownKeys(value, validKeys, name) {
208
+ for (const key of Object.keys(value)) {
209
+ if (!validKeys.includes(key)) {
210
+ throw new Error(`Unknown ${name} key "${key}". Valid keys: ${validKeys.join(", ")}.`);
211
+ }
212
+ }
213
+ }
214
+ function parseConfig(rawConfig, configPath) {
215
+ assertPlainObject(rawConfig, configFileName);
216
+ assertKnownKeys(rawConfig, configKeys, configFileName);
217
+ const config = {};
218
+ if (rawConfig.preset !== undefined) {
219
+ if (typeof rawConfig.preset !== "string") {
220
+ throw new Error(`${configFileName} preset must be a string.`);
221
+ }
222
+ config.preset = resolvePreset(rawConfig.preset);
223
+ }
224
+ if (rawConfig.templateSet !== undefined) {
225
+ if (typeof rawConfig.templateSet !== "string") {
226
+ throw new Error(`${configFileName} templateSet must be a string.`);
227
+ }
228
+ config.templateSet = resolveTemplateSet(rawConfig.templateSet);
229
+ }
230
+ if (rawConfig.aiTools !== undefined) {
231
+ if (!Array.isArray(rawConfig.aiTools)) {
232
+ throw new Error(`${configFileName} aiTools must be an array.`);
233
+ }
234
+ config.aiTools = rawConfig.aiTools.map((aiTool) => {
235
+ if (typeof aiTool !== "string" || !isAiToolName(aiTool)) {
236
+ throw new Error(`Unknown AI tool "${String(aiTool)}". Valid AI tools: ${validAiTools.join(", ")}.`);
237
+ }
238
+ return aiTool;
239
+ });
240
+ }
241
+ if (rawConfig.designSystem !== undefined) {
242
+ if (typeof rawConfig.designSystem !== "string") {
243
+ throw new Error(`${configFileName} designSystem must be a string.`);
244
+ }
245
+ config.designSystem = resolveDesignSystem(rawConfig.designSystem);
246
+ }
247
+ if (rawConfig.personalization !== undefined) {
248
+ assertPlainObject(rawConfig.personalization, `${configFileName} personalization`);
249
+ assertKnownKeys(rawConfig.personalization, personalizationKeys, `${configFileName} personalization`);
250
+ config.personalization = {};
251
+ for (const [key, value] of Object.entries(rawConfig.personalization)) {
252
+ if (typeof value !== "string") {
253
+ throw new Error(`${configFileName} personalization.${key} must be a string.`);
254
+ }
255
+ config.personalization[key] = value;
256
+ }
257
+ }
258
+ if (config.preset === undefined && rawConfig.preset !== undefined) {
259
+ throw new Error(`Invalid preset in ${configPath}.`);
260
+ }
261
+ return config;
262
+ }
263
+ async function readConfig(configPath) {
264
+ let rawContent;
265
+ try {
266
+ rawContent = await readFile(configPath, "utf8");
267
+ }
268
+ catch {
269
+ throw new Error(`Unable to read ${configPath}.`);
270
+ }
271
+ try {
272
+ return parseConfig(JSON.parse(rawContent), configPath);
273
+ }
274
+ catch (error) {
275
+ const message = error instanceof Error ? error.message : String(error);
276
+ throw new Error(`Invalid ${configPath}: ${message}`);
277
+ }
278
+ }
279
+ async function loadConfigForTarget(targetArg) {
280
+ const cwd = process.cwd();
281
+ const targetDir = path.resolve(cwd, targetArg || ".");
282
+ const targetConfigPath = path.join(targetDir, configFileName);
283
+ if (await exists(targetConfigPath)) {
284
+ return readConfig(targetConfigPath);
285
+ }
286
+ const cwdConfigPath = path.join(cwd, configFileName);
287
+ if (targetDir !== cwd && (await exists(cwdConfigPath))) {
288
+ return readConfig(cwdConfigPath);
289
+ }
290
+ return undefined;
291
+ }
292
+ async function applyInitConfig(options, config) {
293
+ if (!config) {
294
+ return;
295
+ }
296
+ options.preset ??= config.preset;
297
+ options.designSystem ??= config.designSystem;
298
+ options.personalization ??= config.personalization;
299
+ options.templateSet ??= config.templateSet;
300
+ options.aiTools ??= config.aiTools;
301
+ }
302
+ function applyUpdateConfig(options, config) {
303
+ if (!config) {
304
+ return;
305
+ }
306
+ options.preset ??= config.preset;
307
+ options.designSystem ??= config.designSystem;
308
+ }
136
309
  export function resolveProjectPreset(projectType) {
137
310
  if (!projectType) {
138
311
  return undefined;
@@ -195,6 +368,33 @@ function cleanPersonalizationValue(value) {
195
368
  const trimmed = value?.trim();
196
369
  return trimmed ? trimmed : undefined;
197
370
  }
371
+ function getResolvedConfig(options) {
372
+ const config = {
373
+ templateSet: options.templateSet ?? "full",
374
+ aiTools: options.aiTools ?? [],
375
+ designSystem: effectiveDesignSystem(options.designSystem),
376
+ };
377
+ const preset = resolvePreset(options.preset);
378
+ if (preset) {
379
+ config.preset = preset;
380
+ }
381
+ if (options.personalization) {
382
+ const personalization = {};
383
+ for (const key of personalizationKeys) {
384
+ const value = cleanPersonalizationValue(options.personalization[key]);
385
+ if (value) {
386
+ personalization[key] = value;
387
+ }
388
+ }
389
+ if (Object.keys(personalization).length > 0) {
390
+ config.personalization = personalization;
391
+ }
392
+ }
393
+ return config;
394
+ }
395
+ function serializeConfig(config) {
396
+ return `${JSON.stringify(config, null, 2)}\n`;
397
+ }
198
398
  function replaceIfProvided(content, placeholder, value) {
199
399
  const replacement = cleanPersonalizationValue(value);
200
400
  return replacement ? content.replaceAll(placeholder, replacement) : content;
@@ -282,10 +482,10 @@ export function personalizeTemplateContent(file, content, values) {
282
482
  }
283
483
  return personalized;
284
484
  }
285
- async function promptForPersonalization() {
485
+ async function promptForPersonalization(defaults) {
286
486
  const shouldPersonalize = await confirm({
287
487
  message: "Personalize template placeholders?",
288
- initialValue: false,
488
+ initialValue: Boolean(defaults),
289
489
  });
290
490
  if (isCancel(shouldPersonalize)) {
291
491
  process.exit(130);
@@ -296,6 +496,7 @@ async function promptForPersonalization() {
296
496
  const projectName = await text({
297
497
  message: "Project name",
298
498
  placeholder: "[Project Name]",
499
+ defaultValue: defaults?.projectName,
299
500
  });
300
501
  if (isCancel(projectName)) {
301
502
  process.exit(130);
@@ -303,6 +504,7 @@ async function promptForPersonalization() {
303
504
  const projectDescription = await text({
304
505
  message: "Short project description",
305
506
  placeholder: "[short project description]",
507
+ defaultValue: defaults?.projectDescription,
306
508
  });
307
509
  if (isCancel(projectDescription)) {
308
510
  process.exit(130);
@@ -310,6 +512,7 @@ async function promptForPersonalization() {
310
512
  const issueTracker = await text({
311
513
  message: "Issue tracker name",
312
514
  placeholder: "Linear or GitHub Issues",
515
+ defaultValue: defaults?.issueTracker,
313
516
  });
314
517
  if (isCancel(issueTracker)) {
315
518
  process.exit(130);
@@ -317,6 +520,7 @@ async function promptForPersonalization() {
317
520
  const designSystemPath = await text({
318
521
  message: "Design system path",
319
522
  placeholder: "docs/design-system.md",
523
+ defaultValue: defaults?.designSystemPath,
320
524
  });
321
525
  if (isCancel(designSystemPath)) {
322
526
  process.exit(130);
@@ -324,6 +528,7 @@ async function promptForPersonalization() {
324
528
  const briefsPath = await text({
325
529
  message: "Briefs path",
326
530
  placeholder: "docs/briefs",
531
+ defaultValue: defaults?.briefsPath,
327
532
  });
328
533
  if (isCancel(briefsPath)) {
329
534
  process.exit(130);
@@ -331,6 +536,7 @@ async function promptForPersonalization() {
331
536
  const testCommand = await text({
332
537
  message: "Test command",
333
538
  placeholder: "npm test",
539
+ defaultValue: defaults?.testCommand,
334
540
  });
335
541
  if (isCancel(testCommand)) {
336
542
  process.exit(130);
@@ -338,6 +544,7 @@ async function promptForPersonalization() {
338
544
  const lintCommand = await text({
339
545
  message: "Lint command",
340
546
  placeholder: "npm run lint",
547
+ defaultValue: defaults?.lintCommand,
341
548
  });
342
549
  if (isCancel(lintCommand)) {
343
550
  process.exit(130);
@@ -345,6 +552,7 @@ async function promptForPersonalization() {
345
552
  const buildCommand = await text({
346
553
  message: "Build/check command",
347
554
  placeholder: "npm run build",
555
+ defaultValue: defaults?.buildCommand,
348
556
  });
349
557
  if (isCancel(buildCommand)) {
350
558
  process.exit(130);
@@ -352,6 +560,7 @@ async function promptForPersonalization() {
352
560
  const stackSummary = await text({
353
561
  message: "Stack summary",
354
562
  placeholder: "Next.js, TypeScript, Tailwind CSS, Vitest",
563
+ defaultValue: defaults?.stackSummary,
355
564
  });
356
565
  if (isCancel(stackSummary)) {
357
566
  process.exit(130);
@@ -368,19 +577,37 @@ async function promptForPersonalization() {
368
577
  stackSummary,
369
578
  };
370
579
  }
371
- async function buildInitTemplateContent(file, preset, personalization) {
372
- const content = await buildTemplateContent(file, preset);
580
+ async function buildInitTemplateContent(file, preset, personalization, designSystem) {
581
+ const content = await buildTemplateContent(file, preset, designSystem);
373
582
  return personalizeTemplateContent(file, content, personalization);
374
583
  }
375
584
  async function installTemplates(targetArg, options) {
376
585
  const targetDir = path.resolve(process.cwd(), targetArg || ".");
377
- const files = options.files ?? (await getTemplateFiles());
586
+ const allTemplateFiles = await getTemplateFiles();
587
+ const files = options.files ??
588
+ (options.templateSet || options.aiTools
589
+ ? getSelectedTemplateFiles(options.templateSet ?? "full", options.aiTools ?? [], allTemplateFiles)
590
+ : allTemplateFiles);
378
591
  const preset = resolvePreset(options.preset);
592
+ const designSystem = effectiveDesignSystem(options.designSystem);
379
593
  const created = [];
380
594
  const skipped = [];
381
595
  if (!options.dryRun) {
382
596
  await mkdir(targetDir, { recursive: true });
383
597
  }
598
+ if (options.writeConfig) {
599
+ const destination = path.join(targetDir, configFileName);
600
+ const destinationExists = await exists(destination);
601
+ if (destinationExists && !options.force) {
602
+ skipped.push(configFileName);
603
+ }
604
+ else {
605
+ created.push(configFileName);
606
+ if (!options.dryRun) {
607
+ await writeFile(destination, serializeConfig(getResolvedConfig(options)));
608
+ }
609
+ }
610
+ }
384
611
  for (const file of files) {
385
612
  const destination = path.join(targetDir, file);
386
613
  const destinationExists = await exists(destination);
@@ -391,14 +618,8 @@ async function installTemplates(targetArg, options) {
391
618
  created.push(file);
392
619
  if (!options.dryRun) {
393
620
  await mkdir(path.dirname(destination), { recursive: true });
394
- if (preset && file === "AGENTS.md") {
395
- const content = await buildInitTemplateContent(file, preset, options.personalization);
396
- await writeFile(destination, wrapManagedBlock(file, content));
397
- }
398
- else {
399
- const content = await buildInitTemplateContent(file, preset, options.personalization);
400
- await writeFile(destination, wrapManagedBlock(file, content));
401
- }
621
+ const content = await buildInitTemplateContent(file, preset, options.personalization, designSystem);
622
+ await writeFile(destination, wrapManagedBlock(file, content));
402
623
  }
403
624
  }
404
625
  if (preset) {
@@ -433,13 +654,18 @@ function replaceManagedBlock(file, existingContent, nextContent) {
433
654
  const replacement = wrapManagedBlock(file, nextContent).trimEnd();
434
655
  return `${existingContent.slice(0, startIndex)}${replacement}${existingContent.slice(afterEndIndex)}`;
435
656
  }
436
- async function buildTemplateContent(file, preset) {
657
+ async function buildTemplateContent(file, preset, designSystem) {
437
658
  if (file === "STACK.md") {
438
659
  if (!preset) {
439
660
  throw new Error("STACK.md requires a preset.");
440
661
  }
441
662
  return getStackGuidance(preset);
442
663
  }
664
+ if (file === "DESIGN-SYSTEM.md") {
665
+ const source = path.join(templatesDir, "design-systems", `${designSystem}.md`);
666
+ const content = await readFile(source, "utf8");
667
+ return addStackReference(file, content, preset);
668
+ }
443
669
  const source = path.join(templatesDir, file);
444
670
  const content = await readFile(source, "utf8");
445
671
  return addStackReference(file, content, preset);
@@ -447,6 +673,7 @@ async function buildTemplateContent(file, preset) {
447
673
  async function updateTemplates(targetArg, options) {
448
674
  const targetDir = path.resolve(process.cwd(), targetArg || ".");
449
675
  const preset = resolvePreset(options.preset);
676
+ const designSystem = effectiveDesignSystem(options.designSystem);
450
677
  const files = preset ? [...(await getTemplateFiles()), "STACK.md"] : await getTemplateFiles();
451
678
  const created = [];
452
679
  const updated = [];
@@ -455,7 +682,7 @@ async function updateTemplates(targetArg, options) {
455
682
  const unchanged = [];
456
683
  for (const file of files) {
457
684
  const destination = path.join(targetDir, file);
458
- const nextContent = await buildTemplateContent(file, preset);
685
+ const nextContent = await buildTemplateContent(file, preset, designSystem);
459
686
  const destinationExists = await exists(destination);
460
687
  if (!destinationExists) {
461
688
  created.push(file);
@@ -569,7 +796,7 @@ async function resolveInteractiveTarget(target, options) {
569
796
  }
570
797
  const aiToolResponse = await multiselect({
571
798
  message: "Which AI tools do you use?",
572
- initialValues: ["codex", "cursor", "claude"],
799
+ initialValues: options.aiTools ?? ["codex", "cursor", "claude"],
573
800
  options: [
574
801
  { label: "Codex", value: "codex" },
575
802
  { label: "Cursor", value: "cursor" },
@@ -582,7 +809,7 @@ async function resolveInteractiveTarget(target, options) {
582
809
  }
583
810
  const templateSetResponse = await select({
584
811
  message: "Which template set do you want?",
585
- initialValue: "standard",
812
+ initialValue: options.templateSet ?? "standard",
586
813
  options: [
587
814
  { label: "Minimal", value: "minimal" },
588
815
  { label: "Standard", value: "standard" },
@@ -593,7 +820,20 @@ async function resolveInteractiveTarget(target, options) {
593
820
  process.exit(130);
594
821
  }
595
822
  const templateFiles = await getTemplateFiles();
823
+ options.templateSet = templateSetResponse;
824
+ options.aiTools = aiToolResponse;
596
825
  options.files = getSelectedTemplateFiles(templateSetResponse, aiToolResponse, templateFiles);
826
+ if (templateSetResponse === "standard" || templateSetResponse === "full") {
827
+ const designSystemResponse = await select({
828
+ message: "Which design system guidance?",
829
+ initialValue: effectiveDesignSystem(options.designSystem),
830
+ options: validDesignSystems.map((value) => ({ label: designSystemLabels[value], value })),
831
+ });
832
+ if (isCancel(designSystemResponse)) {
833
+ process.exit(130);
834
+ }
835
+ options.designSystem = designSystemResponse;
836
+ }
597
837
  if (!options.force) {
598
838
  const preset = resolvePreset(options.preset);
599
839
  const installFiles = preset ? [...options.files, "STACK.md"] : options.files;
@@ -619,7 +859,7 @@ async function resolveInteractiveTarget(target, options) {
619
859
  options.force = conflictResponse === "overwrite";
620
860
  }
621
861
  }
622
- options.personalization = await promptForPersonalization();
862
+ options.personalization = await promptForPersonalization(options.personalization);
623
863
  return resolvedTarget || ".";
624
864
  }
625
865
  async function main() {
@@ -636,6 +876,12 @@ async function main() {
636
876
  }
637
877
  return;
638
878
  }
879
+ if (process.argv.slice(2).includes("--list-design-systems")) {
880
+ for (const designSystem of validDesignSystems) {
881
+ console.log(designSystem);
882
+ }
883
+ return;
884
+ }
639
885
  const program = new Command();
640
886
  program
641
887
  .name("agentkit")
@@ -643,6 +889,7 @@ async function main() {
643
889
  .version(await readPackageVersion(), "-v, --version")
644
890
  .option("--list", "list bundled template files")
645
891
  .option("--list-presets", "list available presets")
892
+ .option("--list-design-systems", "list available design systems")
646
893
  .addHelpText("after", `
647
894
 
648
895
  Examples:
@@ -651,6 +898,7 @@ Examples:
651
898
  agentkit init --preset next
652
899
  agentkit init ./my-project --yes --dry-run
653
900
  agentkit --list-presets
901
+ agentkit --list-design-systems
654
902
  agentkit --list`);
655
903
  program
656
904
  .command("init")
@@ -660,8 +908,11 @@ Examples:
660
908
  .option("--dry-run", "print planned changes without writing files")
661
909
  .option("-i, --interactive", "prompt for install options")
662
910
  .option("-y, --yes", "accept defaults for non-interactive runs")
911
+ .option("--write-config", "write resolved install defaults to agentkit.config.json")
663
912
  .option("--preset <name>", `install stack-specific guidance (${formatPresetList()})`)
913
+ .option("--design-system <name>", `design system guidance for DESIGN-SYSTEM.md (${formatDesignSystemList()})`)
664
914
  .action(async (target, options) => {
915
+ await applyInitConfig(options, await loadConfigForTarget(target));
665
916
  const resolvedTarget = await resolveInteractiveTarget(target, options);
666
917
  const result = await installTemplates(resolvedTarget, options);
667
918
  printInstallResult(result, Boolean(options.dryRun));
@@ -672,7 +923,9 @@ Examples:
672
923
  .argument("[target]", "target project directory", ".")
673
924
  .option("--dry-run", "print planned changes without writing files")
674
925
  .option("--preset <name>", `update stack-specific guidance (${formatPresetList()})`)
926
+ .option("--design-system <name>", `design system guidance for DESIGN-SYSTEM.md (${formatDesignSystemList()})`)
675
927
  .action(async (target, options) => {
928
+ applyUpdateConfig(options, await loadConfigForTarget(target));
676
929
  const result = await updateTemplates(target, options);
677
930
  printUpdateResult(result, Boolean(options.dryRun));
678
931
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thomas-agentkit",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Install AI-agent-ready development templates into a project.",
5
5
  "license": "MIT",
6
6
  "repository": {