thomas-agentkit 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -1
- package/dist/cli.js +192 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -66,6 +66,37 @@ 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
|
+
"aiTools": ["codex", "cursor", "claude"],
|
|
76
|
+
"personalization": {
|
|
77
|
+
"projectName": "Acme CRM",
|
|
78
|
+
"projectDescription": "a customer operations dashboard",
|
|
79
|
+
"issueTracker": "Linear",
|
|
80
|
+
"designSystemPath": "docs/design-system.md",
|
|
81
|
+
"briefsPath": "docs/briefs",
|
|
82
|
+
"testCommand": "pnpm test",
|
|
83
|
+
"lintCommand": "pnpm lint",
|
|
84
|
+
"buildCommand": "pnpm build",
|
|
85
|
+
"stackSummary": "Next.js, TypeScript, PostgreSQL"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
AgentKit reads `agentkit.config.json` from the target directory first. When installing into another target that does not have a config file, it falls back to the current working directory. Config values are defaults: explicit CLI flags override them.
|
|
91
|
+
|
|
92
|
+
Create a config file from resolved install choices:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
npx thomas-agentkit init --write-config
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`--write-config` writes `agentkit.config.json` in the target directory. Existing config files are skipped by default; use `--force` to overwrite one intentionally.
|
|
99
|
+
|
|
69
100
|
List bundled templates:
|
|
70
101
|
|
|
71
102
|
```bash
|
|
@@ -109,6 +140,8 @@ Generated content
|
|
|
109
140
|
|
|
110
141
|
Interactive personalization only applies during `agentkit init` when files are created or overwritten. It does not write a config file, and `agentkit update` does not reapply personalized values.
|
|
111
142
|
|
|
143
|
+
`agentkit.config.json` can set `preset`, `templateSet`, `aiTools`, and `personalization` defaults. `templateSet` may be `minimal`, `standard`, or `full`; `aiTools` may include `codex`, `cursor`, `claude`, and `copilot`. `agentkit update` only uses config `preset` and continues to update all managed bundled templates.
|
|
144
|
+
|
|
112
145
|
## Presets
|
|
113
146
|
|
|
114
147
|
Presets add stack-specific guidance without scaffolding framework app files.
|
|
@@ -122,7 +155,7 @@ Presets add stack-specific guidance without scaffolding framework app files.
|
|
|
122
155
|
## CLI Reference
|
|
123
156
|
|
|
124
157
|
```text
|
|
125
|
-
agentkit init [target] [--force] [--dry-run] [--interactive] [--yes] [--preset <name>]
|
|
158
|
+
agentkit init [target] [--force] [--dry-run] [--interactive] [--yes] [--write-config] [--preset <name>]
|
|
126
159
|
agentkit update [target] [--dry-run] [--preset <name>]
|
|
127
160
|
agentkit --list
|
|
128
161
|
agentkit --list-presets
|
|
@@ -136,6 +169,7 @@ Options:
|
|
|
136
169
|
- `--dry-run`: print planned changes without writing files
|
|
137
170
|
- `-i, --interactive`: explicitly prompt for install options
|
|
138
171
|
- `-y, --yes`: accept defaults without prompts
|
|
172
|
+
- `--write-config`: write resolved install defaults to `agentkit.config.json`
|
|
139
173
|
- `--preset <name>`: install stack-specific guidance (`next`, `sveltekit`, `express`, `convex`, `fullstack`)
|
|
140
174
|
- `--list`: list bundled template files
|
|
141
175
|
- `--list-presets`: list available presets
|
package/dist/cli.js
CHANGED
|
@@ -13,6 +13,20 @@ 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 configFileName = "agentkit.config.json";
|
|
18
|
+
const configKeys = ["preset", "templateSet", "aiTools", "personalization"];
|
|
19
|
+
const personalizationKeys = [
|
|
20
|
+
"projectName",
|
|
21
|
+
"projectDescription",
|
|
22
|
+
"issueTracker",
|
|
23
|
+
"designSystemPath",
|
|
24
|
+
"briefsPath",
|
|
25
|
+
"testCommand",
|
|
26
|
+
"lintCommand",
|
|
27
|
+
"buildCommand",
|
|
28
|
+
"stackSummary",
|
|
29
|
+
];
|
|
16
30
|
const presetLabels = {
|
|
17
31
|
next: "Next.js",
|
|
18
32
|
sveltekit: "SvelteKit",
|
|
@@ -120,9 +134,15 @@ function isProjectTypeName(value) {
|
|
|
120
134
|
function isAiToolName(value) {
|
|
121
135
|
return validAiTools.includes(value);
|
|
122
136
|
}
|
|
137
|
+
function isTemplateSetName(value) {
|
|
138
|
+
return validTemplateSets.includes(value);
|
|
139
|
+
}
|
|
123
140
|
function formatPresetList() {
|
|
124
141
|
return validPresets.join(", ");
|
|
125
142
|
}
|
|
143
|
+
function formatTemplateSetList() {
|
|
144
|
+
return validTemplateSets.join(", ");
|
|
145
|
+
}
|
|
126
146
|
function resolvePreset(preset) {
|
|
127
147
|
if (!preset) {
|
|
128
148
|
return undefined;
|
|
@@ -133,6 +153,115 @@ function resolvePreset(preset) {
|
|
|
133
153
|
}
|
|
134
154
|
return normalizedPreset;
|
|
135
155
|
}
|
|
156
|
+
function resolveTemplateSet(templateSet) {
|
|
157
|
+
if (!templateSet) {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
const normalizedTemplateSet = templateSet.toLowerCase();
|
|
161
|
+
if (!isTemplateSetName(normalizedTemplateSet)) {
|
|
162
|
+
throw new Error(`Unknown template set "${templateSet}". Valid template sets: ${formatTemplateSetList()}.`);
|
|
163
|
+
}
|
|
164
|
+
return normalizedTemplateSet;
|
|
165
|
+
}
|
|
166
|
+
function assertPlainObject(value, name) {
|
|
167
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
168
|
+
throw new Error(`${name} must be an object.`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function assertKnownKeys(value, validKeys, name) {
|
|
172
|
+
for (const key of Object.keys(value)) {
|
|
173
|
+
if (!validKeys.includes(key)) {
|
|
174
|
+
throw new Error(`Unknown ${name} key "${key}". Valid keys: ${validKeys.join(", ")}.`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function parseConfig(rawConfig, configPath) {
|
|
179
|
+
assertPlainObject(rawConfig, configFileName);
|
|
180
|
+
assertKnownKeys(rawConfig, configKeys, configFileName);
|
|
181
|
+
const config = {};
|
|
182
|
+
if (rawConfig.preset !== undefined) {
|
|
183
|
+
if (typeof rawConfig.preset !== "string") {
|
|
184
|
+
throw new Error(`${configFileName} preset must be a string.`);
|
|
185
|
+
}
|
|
186
|
+
config.preset = resolvePreset(rawConfig.preset);
|
|
187
|
+
}
|
|
188
|
+
if (rawConfig.templateSet !== undefined) {
|
|
189
|
+
if (typeof rawConfig.templateSet !== "string") {
|
|
190
|
+
throw new Error(`${configFileName} templateSet must be a string.`);
|
|
191
|
+
}
|
|
192
|
+
config.templateSet = resolveTemplateSet(rawConfig.templateSet);
|
|
193
|
+
}
|
|
194
|
+
if (rawConfig.aiTools !== undefined) {
|
|
195
|
+
if (!Array.isArray(rawConfig.aiTools)) {
|
|
196
|
+
throw new Error(`${configFileName} aiTools must be an array.`);
|
|
197
|
+
}
|
|
198
|
+
config.aiTools = rawConfig.aiTools.map((aiTool) => {
|
|
199
|
+
if (typeof aiTool !== "string" || !isAiToolName(aiTool)) {
|
|
200
|
+
throw new Error(`Unknown AI tool "${String(aiTool)}". Valid AI tools: ${validAiTools.join(", ")}.`);
|
|
201
|
+
}
|
|
202
|
+
return aiTool;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
if (rawConfig.personalization !== undefined) {
|
|
206
|
+
assertPlainObject(rawConfig.personalization, `${configFileName} personalization`);
|
|
207
|
+
assertKnownKeys(rawConfig.personalization, personalizationKeys, `${configFileName} personalization`);
|
|
208
|
+
config.personalization = {};
|
|
209
|
+
for (const [key, value] of Object.entries(rawConfig.personalization)) {
|
|
210
|
+
if (typeof value !== "string") {
|
|
211
|
+
throw new Error(`${configFileName} personalization.${key} must be a string.`);
|
|
212
|
+
}
|
|
213
|
+
config.personalization[key] = value;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (config.preset === undefined && rawConfig.preset !== undefined) {
|
|
217
|
+
throw new Error(`Invalid preset in ${configPath}.`);
|
|
218
|
+
}
|
|
219
|
+
return config;
|
|
220
|
+
}
|
|
221
|
+
async function readConfig(configPath) {
|
|
222
|
+
let rawContent;
|
|
223
|
+
try {
|
|
224
|
+
rawContent = await readFile(configPath, "utf8");
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
throw new Error(`Unable to read ${configPath}.`);
|
|
228
|
+
}
|
|
229
|
+
try {
|
|
230
|
+
return parseConfig(JSON.parse(rawContent), configPath);
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
234
|
+
throw new Error(`Invalid ${configPath}: ${message}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
async function loadConfigForTarget(targetArg) {
|
|
238
|
+
const cwd = process.cwd();
|
|
239
|
+
const targetDir = path.resolve(cwd, targetArg || ".");
|
|
240
|
+
const targetConfigPath = path.join(targetDir, configFileName);
|
|
241
|
+
if (await exists(targetConfigPath)) {
|
|
242
|
+
return readConfig(targetConfigPath);
|
|
243
|
+
}
|
|
244
|
+
const cwdConfigPath = path.join(cwd, configFileName);
|
|
245
|
+
if (targetDir !== cwd && (await exists(cwdConfigPath))) {
|
|
246
|
+
return readConfig(cwdConfigPath);
|
|
247
|
+
}
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
250
|
+
async function applyInitConfig(options, config) {
|
|
251
|
+
if (!config) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
options.preset ??= config.preset;
|
|
255
|
+
options.personalization ??= config.personalization;
|
|
256
|
+
options.templateSet ??= config.templateSet;
|
|
257
|
+
options.aiTools ??= config.aiTools;
|
|
258
|
+
}
|
|
259
|
+
function applyUpdateConfig(options, config) {
|
|
260
|
+
if (!config) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
options.preset ??= config.preset;
|
|
264
|
+
}
|
|
136
265
|
export function resolveProjectPreset(projectType) {
|
|
137
266
|
if (!projectType) {
|
|
138
267
|
return undefined;
|
|
@@ -195,6 +324,32 @@ function cleanPersonalizationValue(value) {
|
|
|
195
324
|
const trimmed = value?.trim();
|
|
196
325
|
return trimmed ? trimmed : undefined;
|
|
197
326
|
}
|
|
327
|
+
function getResolvedConfig(options) {
|
|
328
|
+
const config = {
|
|
329
|
+
templateSet: options.templateSet ?? "full",
|
|
330
|
+
aiTools: options.aiTools ?? [],
|
|
331
|
+
};
|
|
332
|
+
const preset = resolvePreset(options.preset);
|
|
333
|
+
if (preset) {
|
|
334
|
+
config.preset = preset;
|
|
335
|
+
}
|
|
336
|
+
if (options.personalization) {
|
|
337
|
+
const personalization = {};
|
|
338
|
+
for (const key of personalizationKeys) {
|
|
339
|
+
const value = cleanPersonalizationValue(options.personalization[key]);
|
|
340
|
+
if (value) {
|
|
341
|
+
personalization[key] = value;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (Object.keys(personalization).length > 0) {
|
|
345
|
+
config.personalization = personalization;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return config;
|
|
349
|
+
}
|
|
350
|
+
function serializeConfig(config) {
|
|
351
|
+
return `${JSON.stringify(config, null, 2)}\n`;
|
|
352
|
+
}
|
|
198
353
|
function replaceIfProvided(content, placeholder, value) {
|
|
199
354
|
const replacement = cleanPersonalizationValue(value);
|
|
200
355
|
return replacement ? content.replaceAll(placeholder, replacement) : content;
|
|
@@ -282,10 +437,10 @@ export function personalizeTemplateContent(file, content, values) {
|
|
|
282
437
|
}
|
|
283
438
|
return personalized;
|
|
284
439
|
}
|
|
285
|
-
async function promptForPersonalization() {
|
|
440
|
+
async function promptForPersonalization(defaults) {
|
|
286
441
|
const shouldPersonalize = await confirm({
|
|
287
442
|
message: "Personalize template placeholders?",
|
|
288
|
-
initialValue:
|
|
443
|
+
initialValue: Boolean(defaults),
|
|
289
444
|
});
|
|
290
445
|
if (isCancel(shouldPersonalize)) {
|
|
291
446
|
process.exit(130);
|
|
@@ -296,6 +451,7 @@ async function promptForPersonalization() {
|
|
|
296
451
|
const projectName = await text({
|
|
297
452
|
message: "Project name",
|
|
298
453
|
placeholder: "[Project Name]",
|
|
454
|
+
defaultValue: defaults?.projectName,
|
|
299
455
|
});
|
|
300
456
|
if (isCancel(projectName)) {
|
|
301
457
|
process.exit(130);
|
|
@@ -303,6 +459,7 @@ async function promptForPersonalization() {
|
|
|
303
459
|
const projectDescription = await text({
|
|
304
460
|
message: "Short project description",
|
|
305
461
|
placeholder: "[short project description]",
|
|
462
|
+
defaultValue: defaults?.projectDescription,
|
|
306
463
|
});
|
|
307
464
|
if (isCancel(projectDescription)) {
|
|
308
465
|
process.exit(130);
|
|
@@ -310,6 +467,7 @@ async function promptForPersonalization() {
|
|
|
310
467
|
const issueTracker = await text({
|
|
311
468
|
message: "Issue tracker name",
|
|
312
469
|
placeholder: "Linear or GitHub Issues",
|
|
470
|
+
defaultValue: defaults?.issueTracker,
|
|
313
471
|
});
|
|
314
472
|
if (isCancel(issueTracker)) {
|
|
315
473
|
process.exit(130);
|
|
@@ -317,6 +475,7 @@ async function promptForPersonalization() {
|
|
|
317
475
|
const designSystemPath = await text({
|
|
318
476
|
message: "Design system path",
|
|
319
477
|
placeholder: "docs/design-system.md",
|
|
478
|
+
defaultValue: defaults?.designSystemPath,
|
|
320
479
|
});
|
|
321
480
|
if (isCancel(designSystemPath)) {
|
|
322
481
|
process.exit(130);
|
|
@@ -324,6 +483,7 @@ async function promptForPersonalization() {
|
|
|
324
483
|
const briefsPath = await text({
|
|
325
484
|
message: "Briefs path",
|
|
326
485
|
placeholder: "docs/briefs",
|
|
486
|
+
defaultValue: defaults?.briefsPath,
|
|
327
487
|
});
|
|
328
488
|
if (isCancel(briefsPath)) {
|
|
329
489
|
process.exit(130);
|
|
@@ -331,6 +491,7 @@ async function promptForPersonalization() {
|
|
|
331
491
|
const testCommand = await text({
|
|
332
492
|
message: "Test command",
|
|
333
493
|
placeholder: "npm test",
|
|
494
|
+
defaultValue: defaults?.testCommand,
|
|
334
495
|
});
|
|
335
496
|
if (isCancel(testCommand)) {
|
|
336
497
|
process.exit(130);
|
|
@@ -338,6 +499,7 @@ async function promptForPersonalization() {
|
|
|
338
499
|
const lintCommand = await text({
|
|
339
500
|
message: "Lint command",
|
|
340
501
|
placeholder: "npm run lint",
|
|
502
|
+
defaultValue: defaults?.lintCommand,
|
|
341
503
|
});
|
|
342
504
|
if (isCancel(lintCommand)) {
|
|
343
505
|
process.exit(130);
|
|
@@ -345,6 +507,7 @@ async function promptForPersonalization() {
|
|
|
345
507
|
const buildCommand = await text({
|
|
346
508
|
message: "Build/check command",
|
|
347
509
|
placeholder: "npm run build",
|
|
510
|
+
defaultValue: defaults?.buildCommand,
|
|
348
511
|
});
|
|
349
512
|
if (isCancel(buildCommand)) {
|
|
350
513
|
process.exit(130);
|
|
@@ -352,6 +515,7 @@ async function promptForPersonalization() {
|
|
|
352
515
|
const stackSummary = await text({
|
|
353
516
|
message: "Stack summary",
|
|
354
517
|
placeholder: "Next.js, TypeScript, Tailwind CSS, Vitest",
|
|
518
|
+
defaultValue: defaults?.stackSummary,
|
|
355
519
|
});
|
|
356
520
|
if (isCancel(stackSummary)) {
|
|
357
521
|
process.exit(130);
|
|
@@ -374,13 +538,30 @@ async function buildInitTemplateContent(file, preset, personalization) {
|
|
|
374
538
|
}
|
|
375
539
|
async function installTemplates(targetArg, options) {
|
|
376
540
|
const targetDir = path.resolve(process.cwd(), targetArg || ".");
|
|
377
|
-
const
|
|
541
|
+
const allTemplateFiles = await getTemplateFiles();
|
|
542
|
+
const files = options.files ??
|
|
543
|
+
(options.templateSet || options.aiTools
|
|
544
|
+
? getSelectedTemplateFiles(options.templateSet ?? "full", options.aiTools ?? [], allTemplateFiles)
|
|
545
|
+
: allTemplateFiles);
|
|
378
546
|
const preset = resolvePreset(options.preset);
|
|
379
547
|
const created = [];
|
|
380
548
|
const skipped = [];
|
|
381
549
|
if (!options.dryRun) {
|
|
382
550
|
await mkdir(targetDir, { recursive: true });
|
|
383
551
|
}
|
|
552
|
+
if (options.writeConfig) {
|
|
553
|
+
const destination = path.join(targetDir, configFileName);
|
|
554
|
+
const destinationExists = await exists(destination);
|
|
555
|
+
if (destinationExists && !options.force) {
|
|
556
|
+
skipped.push(configFileName);
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
created.push(configFileName);
|
|
560
|
+
if (!options.dryRun) {
|
|
561
|
+
await writeFile(destination, serializeConfig(getResolvedConfig(options)));
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
384
565
|
for (const file of files) {
|
|
385
566
|
const destination = path.join(targetDir, file);
|
|
386
567
|
const destinationExists = await exists(destination);
|
|
@@ -569,7 +750,7 @@ async function resolveInteractiveTarget(target, options) {
|
|
|
569
750
|
}
|
|
570
751
|
const aiToolResponse = await multiselect({
|
|
571
752
|
message: "Which AI tools do you use?",
|
|
572
|
-
initialValues: ["codex", "cursor", "claude"],
|
|
753
|
+
initialValues: options.aiTools ?? ["codex", "cursor", "claude"],
|
|
573
754
|
options: [
|
|
574
755
|
{ label: "Codex", value: "codex" },
|
|
575
756
|
{ label: "Cursor", value: "cursor" },
|
|
@@ -582,7 +763,7 @@ async function resolveInteractiveTarget(target, options) {
|
|
|
582
763
|
}
|
|
583
764
|
const templateSetResponse = await select({
|
|
584
765
|
message: "Which template set do you want?",
|
|
585
|
-
initialValue: "standard",
|
|
766
|
+
initialValue: options.templateSet ?? "standard",
|
|
586
767
|
options: [
|
|
587
768
|
{ label: "Minimal", value: "minimal" },
|
|
588
769
|
{ label: "Standard", value: "standard" },
|
|
@@ -593,6 +774,8 @@ async function resolveInteractiveTarget(target, options) {
|
|
|
593
774
|
process.exit(130);
|
|
594
775
|
}
|
|
595
776
|
const templateFiles = await getTemplateFiles();
|
|
777
|
+
options.templateSet = templateSetResponse;
|
|
778
|
+
options.aiTools = aiToolResponse;
|
|
596
779
|
options.files = getSelectedTemplateFiles(templateSetResponse, aiToolResponse, templateFiles);
|
|
597
780
|
if (!options.force) {
|
|
598
781
|
const preset = resolvePreset(options.preset);
|
|
@@ -619,7 +802,7 @@ async function resolveInteractiveTarget(target, options) {
|
|
|
619
802
|
options.force = conflictResponse === "overwrite";
|
|
620
803
|
}
|
|
621
804
|
}
|
|
622
|
-
options.personalization = await promptForPersonalization();
|
|
805
|
+
options.personalization = await promptForPersonalization(options.personalization);
|
|
623
806
|
return resolvedTarget || ".";
|
|
624
807
|
}
|
|
625
808
|
async function main() {
|
|
@@ -660,8 +843,10 @@ Examples:
|
|
|
660
843
|
.option("--dry-run", "print planned changes without writing files")
|
|
661
844
|
.option("-i, --interactive", "prompt for install options")
|
|
662
845
|
.option("-y, --yes", "accept defaults for non-interactive runs")
|
|
846
|
+
.option("--write-config", "write resolved install defaults to agentkit.config.json")
|
|
663
847
|
.option("--preset <name>", `install stack-specific guidance (${formatPresetList()})`)
|
|
664
848
|
.action(async (target, options) => {
|
|
849
|
+
await applyInitConfig(options, await loadConfigForTarget(target));
|
|
665
850
|
const resolvedTarget = await resolveInteractiveTarget(target, options);
|
|
666
851
|
const result = await installTemplates(resolvedTarget, options);
|
|
667
852
|
printInstallResult(result, Boolean(options.dryRun));
|
|
@@ -673,6 +858,7 @@ Examples:
|
|
|
673
858
|
.option("--dry-run", "print planned changes without writing files")
|
|
674
859
|
.option("--preset <name>", `update stack-specific guidance (${formatPresetList()})`)
|
|
675
860
|
.action(async (target, options) => {
|
|
861
|
+
applyUpdateConfig(options, await loadConfigForTarget(target));
|
|
676
862
|
const result = await updateTemplates(target, options);
|
|
677
863
|
printUpdateResult(result, Boolean(options.dryRun));
|
|
678
864
|
});
|