@tsfpp/agents 1.2.0 → 1.2.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.
Files changed (3) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/init.mjs +59 -47
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -10,6 +10,20 @@ Versioning follows [Semantic Versioning](https://semver.org/).
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [1.2.2] - 2026-05-16
14
+
15
+ ### Fixed
16
+
17
+ - Updated `init.mjs` questionnaire flow to avoid overwriting existing `tsconfig` and ESLint config files.
18
+ - Added `N`/skip escape hatch for existing-file prompts.
19
+ - Added `SIGINT` handling by wrapping execution in `main` so Ctrl+C exits cleanly without hanging awaits.
20
+
21
+ ## [1.2.1] - 2026-05-16
22
+
23
+ ### Fixed
24
+
25
+ - Fixed an `init.mjs` idempotency bug in ESLint config declaration handling.
26
+
13
27
  ## [1.2.0] - 2026-05-16
14
28
 
15
29
  ### Added
package/init.mjs CHANGED
@@ -99,7 +99,9 @@ async function askProfile(label) {
99
99
  console.log(` ${dim('1')} base ${dim('— TypeScript / Node.js')}`);
100
100
  console.log(` ${dim('2')} react ${dim('— React / TSX')}`);
101
101
  console.log(` ${dim('3')} api ${dim('— HTTP API / Node.js servers')}`);
102
- const choice = await ask(` ${dim('[1/2/3, default: 1]')} `);
102
+ console.log(` ${dim('n')} skip ${dim('— keep existing / do not generate')}`);
103
+ const choice = await ask(` ${dim('[1/2/3/n, default: 1]')} `);
104
+ if (choice === 'n') return null;
103
105
  return choice === '2' ? 'react' : choice === '3' ? 'api' : 'base';
104
106
  }
105
107
 
@@ -146,7 +148,7 @@ function generateSingleConfig(profile) {
146
148
  return `${imp}\nexport default [...${spread}]\n`;
147
149
  }
148
150
 
149
- async function writeEslintConfig() {
151
+ async function writeEslintConfig(results) {
150
152
  const packages = await detectWorkspacePackages();
151
153
 
152
154
  let content;
@@ -158,12 +160,17 @@ async function writeEslintConfig() {
158
160
  for (const pkg of packages) {
159
161
  packageProfiles[pkg] = await askProfile(pkg);
160
162
  }
161
- content = generateMonorepoConfig(packageProfiles);
163
+ const activeProfiles = Object.fromEntries(
164
+ Object.entries(packageProfiles).filter(([, p]) => p !== null)
165
+ );
166
+ if (Object.keys(activeProfiles).length === 0) return;
167
+ content = generateMonorepoConfig(activeProfiles);
162
168
  description = 'monorepo';
163
169
  } else {
164
170
  const profile = await askProfile('this project');
165
- content = generateSingleConfig(profile);
166
- description = `profile: ${profile}`;
171
+ if (profile === null) return;
172
+ content = generateSingleConfig(profile);
173
+ description = `profile: ${profile}`;
167
174
  }
168
175
 
169
176
  try {
@@ -199,11 +206,17 @@ async function confirm(question) {
199
206
 
200
207
  // ─── Main ─────────────────────────────────────────────────────────────────────
201
208
 
202
- console.log();
203
- console.log(bold(' @tsfpp/agents — init'));
204
- console.log(dim(' Sets up Copilot agents, instructions, prompts, skills, and ESLint config.\n'));
209
+ process.on('SIGINT', () => {
210
+ console.log('\n\n Aborted.\n');
211
+ process.exit(0);
212
+ });
205
213
 
206
- const results = { copied: [], skipped: [], failed: [] };
214
+ async function main() {
215
+ console.log();
216
+ console.log(bold(' @tsfpp/agents — init'));
217
+ console.log(dim(' Sets up Copilot agents, instructions, prompts, skills, and ESLint config.\n'));
218
+
219
+ const results = { copied: [], skipped: [], failed: [] };
207
220
 
208
221
  // ── Copy files ────────────────────────────────────────────────────────────────
209
222
 
@@ -240,45 +253,32 @@ console.log();
240
253
  const eslintDest = join(cwd, 'eslint.config.js');
241
254
 
242
255
  if (existsSync(eslintDest)) {
243
- const overwrite = await confirm(
244
- ` ${yellow('!')} eslint.config.js already exists. Overwrite? ${dim('[y/N]')} `
245
- );
246
- if (!overwrite) {
256
+ if (yes) {
247
257
  results.skipped.push('eslint.config.js');
248
- console.log(` ${dim('–')} ${dim('eslint.config.js')} ${dim('(skipped)')}`);
258
+ console.log(` ${dim('–')} ${dim('eslint.config.js')} ${dim('(skipped — project-managed)')}`);
249
259
  } else {
250
- await writeEslintConfig();
260
+ const overwrite = await confirm(
261
+ ` ${yellow('!')} eslint.config.js already exists. Overwrite? ${dim('[y/N]')} `
262
+ );
263
+ if (overwrite) await writeEslintConfig(results);
264
+ else {
265
+ results.skipped.push('eslint.config.js');
266
+ console.log(` ${dim('–')} ${dim('eslint.config.js')} ${dim('(skipped)')}`);
267
+ }
251
268
  }
252
269
  } else {
253
- await writeEslintConfig();
270
+ await writeEslintConfig(results);
254
271
  }
255
272
 
256
- async function writeEslintConfig() {
257
- console.log(` Which ESLint profile does this project use?`);
258
- console.log(` ${dim('1')} base ${dim('— TypeScript / Node.js')}`);
259
- console.log(` ${dim('2')} react ${dim('— React / TSX')}`);
260
- console.log(` ${dim('3')} api ${dim('— HTTP API / Node.js servers')}`);
261
273
 
262
- const choice = await ask(` ${dim('[1/2/3, default: 1]')} `);
263
- const profile = choice === '2' ? 'react' : choice === '3' ? 'api' : 'base';
264
-
265
- try {
266
- await writeFile(eslintDest, ESLINT_PROFILES[profile], 'utf8');
267
- results.copied.push('eslint.config.js');
268
- console.log(` ${green('✓')} eslint.config.js ${dim(`(profile: ${profile})`)}`);
269
- } catch (err) {
270
- results.failed.push('eslint.config.js');
271
- console.log(` \x1b[31m✗\x1b[0m eslint.config.js ${dim(`(${err.message})`)}`);
272
- }
273
- }
274
274
 
275
275
  // ── Generate tsconfig.json ────────────────────────────────────────────────────
276
276
 
277
277
  console.log();
278
278
 
279
- await writeTsConfigs(await detectWorkspacePackages());
279
+ await writeTsConfigs(await detectWorkspacePackages(), results);
280
280
 
281
- async function writeTsConfigs(packages) {
281
+ async function writeTsConfigs(packages, results) {
282
282
  const PRESETS = {
283
283
  app: { extends: '@tsfpp/tsconfig/app', label: 'app — application / tool (noEmit)' },
284
284
  lib: { extends: '@tsfpp/tsconfig/lib', label: 'lib — publishable package (declaration, composite)' },
@@ -288,7 +288,9 @@ async function writeTsConfigs(packages) {
288
288
  console.log(`\n tsconfig preset for ${bold(label)}:`);
289
289
  console.log(` ${dim('1')} app ${dim('— application / tool (noEmit: true)')}`);
290
290
  console.log(` ${dim('2')} lib ${dim('— publishable package (declaration, composite)')}`);
291
- const choice = await ask(` ${dim('[1/2, default: 1]')} `);
291
+ console.log(` ${dim('n')} skip ${dim('— keep existing / do not generate')}`);
292
+ const choice = await ask(` ${dim('[1/2/n, default: 1]')} `);
293
+ if (choice === 'n') return null;
292
294
  return choice === '2' ? 'lib' : 'app';
293
295
  }
294
296
 
@@ -309,6 +311,11 @@ async function writeTsConfigs(packages) {
309
311
 
310
312
  async function writeIfConfirmed(destPath, content, label) {
311
313
  if (existsSync(destPath)) {
314
+ if (yes) {
315
+ results.skipped.push(label);
316
+ console.log(` ${dim('–')} ${dim(label)} ${dim('(skipped — project-managed)')}`);
317
+ return;
318
+ }
312
319
  const overwrite = await confirm(
313
320
  ` ${yellow('!')} ${label} already exists. Overwrite? ${dim('[y/N]')} `
314
321
  );
@@ -338,6 +345,7 @@ async function writeTsConfigs(packages) {
338
345
  }
339
346
 
340
347
  for (const [pkg, preset] of Object.entries(packagePresets)) {
348
+ if (preset === null) continue;
341
349
  const destPath = join(cwd, pkg, 'tsconfig.json');
342
350
  const content = generateTsConfig(preset);
343
351
  await writeIfConfirmed(destPath, content, `${pkg}/tsconfig.json`);
@@ -349,22 +357,26 @@ async function writeTsConfigs(packages) {
349
357
  await writeIfConfirmed(rootDest, rootContent, 'tsconfig.json (root references)');
350
358
  } else {
351
359
  // Single package
352
- const preset = await askPreset('this project');
360
+ const preset = await askPreset('this project');
361
+ if (preset === null) return;
353
362
  const dest = join(cwd, 'tsconfig.json');
354
363
  const content = generateTsConfig(preset);
355
364
  await writeIfConfirmed(dest, content, 'tsconfig.json');
356
365
  }
357
366
  }
358
367
 
359
- console.log();
360
- console.log(dim(' ─────────────────────────────────────────'));
361
- console.log(` ${green(results.copied.length + ' copied')} ${yellow(results.skipped.length + ' skipped')} ${results.failed.length > 0 ? `\x1b[31m${results.failed.length} failed\x1b[0m` : dim('0 failed')}`);
362
- console.log();
368
+ console.log();
369
+ console.log(dim(' ─────────────────────────────────────────'));
370
+ console.log(` ${green(results.copied.length + ' copied')} ${yellow(results.skipped.length + ' skipped')} ${results.failed.length > 0 ? `\x1b[31m${results.failed.length} failed\x1b[0m` : dim('0 failed')}`);
371
+ console.log();
363
372
 
364
- if (results.failed.length === 0) {
365
- console.log(' ' + bold('Done.') + ' Reload VS Code to activate Copilot instructions.');
366
- console.log(dim(' Commit the generated files — they are workspace configuration.\n'));
367
- } else {
368
- console.log(' Some files could not be copied. Check the errors above.\n');
369
- process.exit(1);
373
+ if (results.failed.length === 0) {
374
+ console.log(' ' + bold('Done.') + ' Reload VS Code to activate Copilot instructions.');
375
+ console.log(dim(' Commit the generated files — they are workspace configuration.\n'));
376
+ } else {
377
+ console.log(' Some files could not be copied. Check the errors above.\n');
378
+ process.exit(1);
379
+ }
370
380
  }
381
+
382
+ main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tsfpp/agents",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Workspace AI tooling for TSF++ projects: scoped instructions, coding agents, and reusable prompts",
5
5
  "keywords": [
6
6
  "tsfpp",