@relipa/ai-flow-kit 0.0.5 → 0.0.6-beta.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/scripts/init.js CHANGED
@@ -2,7 +2,7 @@ const fs = require('fs-extra');
2
2
  const path = require('path');
3
3
  const os = require('os');
4
4
  const chalk = require('chalk');
5
- const { input, select, confirm } = require('@inquirer/prompts');
5
+ const { input, checkbox, confirm } = require('@inquirer/prompts');
6
6
 
7
7
  const PKG_DIR = path.join(__dirname, '..');
8
8
  const GLOBAL_AIFLOW_DIR = path.join(os.homedir(), '.aiflow');
@@ -23,7 +23,7 @@ const AI_TOOL_FILES = {
23
23
  'cursor': '.cursorrules',
24
24
  'gemini': 'GEMINI.md',
25
25
  'copilot': '.github/copilot-instructions.md',
26
- 'generic': '.aiflow/AI_INSTRUCTIONS.md'
26
+ 'generic': '.aiflow/docs/AI_INSTRUCTIONS.md',
27
27
  };
28
28
 
29
29
  /**
@@ -79,15 +79,14 @@ async function detectFramework(projectDir) {
79
79
  }
80
80
 
81
81
  const FRAMEWORK_CHOICES = [
82
- { name: 'Spring Boot (Java)', value: 'spring-boot' },
83
- { name: 'React', value: 'reactjs' },
84
- { name: 'Next.js', value: 'nextjs' },
85
- { name: 'Vue / Nuxt', value: 'vue-nuxt' },
86
- { name: 'Laravel (PHP)', value: 'laravel' },
87
- { name: 'Node.js / Express', value: 'nodejs-express' },
88
- { name: 'Python Django', value: 'python-django' },
89
- { name: 'Python FastAPI', value: 'python-fastapi' },
90
- { name: 'Skip (no framework)', value: null },
82
+ { name: 'Spring Boot (Java)', value: 'spring-boot' },
83
+ { name: 'React', value: 'reactjs' },
84
+ { name: 'Next.js', value: 'nextjs' },
85
+ { name: 'Vue / Nuxt', value: 'vue-nuxt' },
86
+ { name: 'Laravel (PHP)', value: 'laravel' },
87
+ { name: 'Node.js / Express', value: 'nodejs-express' },
88
+ { name: 'Python Django', value: 'python-django' },
89
+ { name: 'Python FastAPI', value: 'python-fastapi' },
91
90
  ];
92
91
 
93
92
  async function copyAssetsToVersion(versionDir, framework) {
@@ -279,32 +278,20 @@ async function setupFramework(projectDir, framework, multi = false, selectedTool
279
278
  const fileExists = await fs.pathExists(targetPath);
280
279
 
281
280
  if (!multi) {
281
+ const relPath = path.relative(projectDir, targetPath).replace(/\\/g, '/');
282
282
  if (fileExists) {
283
- const relPath = path.relative(projectDir, targetPath).replace(/\\/g, '/');
284
- const overwrite = await confirm({
285
- message: ` ${relPath} already exists overwrite with aiflow template?`,
286
- default: false
287
- });
288
-
289
- if (!overwrite) {
290
- // Save aiflow template to .aiflow/reference/ so dev can compare/merge manually
291
- const refPath = path.join(projectDir, '.aiflow', 'reference', path.basename(targetPath));
292
- await fs.ensureDir(path.dirname(refPath));
293
- await fs.writeFile(refPath, finalContent);
294
- const refRel = path.relative(projectDir, refPath).replace(/\\/g, '/');
295
- skipped.push(relPath);
296
- console.log(chalk.gray(` aiflow template saved to ${refRel} for reference`));
297
- continue;
298
- }
299
-
300
- // Backup existing file before overwriting
301
- const backupPath = path.join(projectDir, '.aiflow', 'backup', path.basename(targetPath));
302
- await fs.ensureDir(path.dirname(backupPath));
303
- await fs.copy(targetPath, backupPath, { overwrite: true });
304
- console.log(chalk.gray(` Backed up ${relPath} → .aiflow/backup/${path.basename(targetPath)}`));
283
+ // File already exists — save aiflow template to .aiflow/reference/ for comparison.
284
+ // Developer owns the file in root; we never overwrite without explicit action.
285
+ const refPath = path.join(projectDir, '.aiflow', 'reference', path.basename(targetPath));
286
+ await fs.ensureDir(path.dirname(refPath));
287
+ await fs.writeFile(refPath, finalContent);
288
+ const refRel = path.relative(projectDir, refPath).replace(/\\/g, '/');
289
+ console.log(chalk.gray(` ✓ ${relPath} exists — aiflow template saved to ${refRel}`));
290
+ skipped.push(relPath);
291
+ } else {
292
+ await fs.writeFile(targetPath, finalContent);
293
+ written.push(AI_TOOL_FILES[tool]);
305
294
  }
306
- await fs.writeFile(targetPath, finalContent);
307
- written.push(AI_TOOL_FILES[tool]);
308
295
  } else {
309
296
  if (fileExists) {
310
297
  await fs.appendFile(targetPath, separator + frameworkContent);
@@ -536,6 +523,74 @@ function maskSecret(value) {
536
523
  return `${value.slice(0, 4)}***${value.slice(-4)}`;
537
524
  }
538
525
 
526
+ // ── RTK (token compression) helpers ──────────────────────────────
527
+
528
+ async function detectRtk() {
529
+ const { execSync } = require('child_process');
530
+ try {
531
+ const out = execSync('rtk --version', { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
532
+ return { installed: true, version: out.replace(/^rtk\s*/i, '') };
533
+ } catch (_) {
534
+ return { installed: false };
535
+ }
536
+ }
537
+
538
+ async function isRtkHookConfigured(projectDir) {
539
+ const settingsPath = path.join(projectDir, '.claude', 'settings.json');
540
+ if (!(await fs.pathExists(settingsPath))) return false;
541
+ const settings = await fs.readJson(settingsPath).catch(() => ({}));
542
+ const preHooks = settings?.hooks?.PreToolUse || [];
543
+ return preHooks.some(h =>
544
+ (h.hooks || []).some(entry =>
545
+ typeof entry.command === 'string' && entry.command.includes('rtk')
546
+ )
547
+ );
548
+ }
549
+
550
+ async function setupRtkHook(projectDir) {
551
+ const settingsPath = path.join(projectDir, '.claude', 'settings.json');
552
+ let settings = {};
553
+ if (await fs.pathExists(settingsPath)) {
554
+ settings = await fs.readJson(settingsPath).catch(() => ({}));
555
+ }
556
+ if (!settings.hooks) settings.hooks = {};
557
+ if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
558
+
559
+ const alreadySet = settings.hooks.PreToolUse.some(h =>
560
+ (h.hooks || []).some(e => typeof e.command === 'string' && e.command.includes('rtk'))
561
+ );
562
+ if (alreadySet) return;
563
+
564
+ settings.hooks.PreToolUse.push({
565
+ matcher: 'Bash',
566
+ hooks: [{ type: 'command', command: 'rtk rewrite' }],
567
+ });
568
+ await fs.ensureDir(path.join(projectDir, '.claude'));
569
+ await fs.writeJson(settingsPath, settings, { spaces: 2 });
570
+ console.log(chalk.green('✓ RTK token compression hook configured (.claude/settings.json)'));
571
+ }
572
+
573
+ async function maybeSetupRtk(projectDir, forceSetup) {
574
+ const rtk = await detectRtk();
575
+ if (!rtk.installed && !forceSetup) return;
576
+
577
+ if (forceSetup && !rtk.installed) {
578
+ console.log(chalk.yellow('⚠ --with-rtk specified but rtk binary not found. Install: cargo install rtk'));
579
+ return;
580
+ }
581
+
582
+ if (await isRtkHookConfigured(projectDir)) {
583
+ console.log(chalk.gray(' RTK hook already configured — skipping.'));
584
+ return;
585
+ }
586
+
587
+ const enable = forceSetup || await confirm({
588
+ message: `RTK detected (${rtk.version}). Enable token compression for Claude Code?`,
589
+ default: true,
590
+ });
591
+ if (enable) await setupRtkHook(projectDir);
592
+ }
593
+
539
594
  async function init(options) {
540
595
  const projectDir = process.cwd();
541
596
  const aiflowDir = path.join(projectDir, '.aiflow');
@@ -548,27 +603,23 @@ async function init(options) {
548
603
  // ── Auto-detect or prompt for framework if not supplied ──────
549
604
  if (frameworks.length === 0) {
550
605
  const detected = await detectFramework(projectDir);
606
+ const hint = chalk.gray(' Space to select/unselect · Enter to confirm · Leave empty to skip');
551
607
  if (detected) {
552
608
  const label = FRAMEWORK_CHOICES.find(c => c.value === detected)?.name || detected;
553
609
  console.log(chalk.cyan(`\n Detected framework: ${chalk.bold(label)}`));
554
- const useDetected = await confirm({ message: `Use "${label}"?`, default: true });
555
- if (useDetected) {
556
- frameworks = [detected];
557
- } else {
558
- const chosen = await select({
559
- message: 'Select framework:',
560
- choices: FRAMEWORK_CHOICES,
561
- });
562
- if (chosen) frameworks = [chosen];
563
- }
610
+ console.log(hint);
564
611
  } else {
565
612
  console.log(chalk.gray('\n No framework detected.'));
566
- const chosen = await select({
567
- message: 'Select framework (or skip):',
568
- choices: FRAMEWORK_CHOICES,
569
- });
570
- if (chosen) frameworks = [chosen];
613
+ console.log(hint);
571
614
  }
615
+ const chosen = await checkbox({
616
+ message: 'Select framework(s):',
617
+ choices: FRAMEWORK_CHOICES.map(c => ({
618
+ ...c,
619
+ checked: c.value === detected,
620
+ })),
621
+ });
622
+ frameworks = chosen;
572
623
  }
573
624
 
574
625
  const primaryFramework = frameworks[0] || null;
@@ -600,6 +651,12 @@ async function init(options) {
600
651
  await fs.writeJson(stateFile, state);
601
652
  }
602
653
 
654
+ // ── Optional: RTK token compression ─────────────────────────
655
+ // Commander sets options.rtk=false when --no-rtk is passed
656
+ if (options.rtk !== false) {
657
+ await maybeSetupRtk(projectDir, options.withRtk || false);
658
+ }
659
+
603
660
  console.log(chalk.green('\n✨ Initialized AI Flow Kit successfully!'));
604
661
  console.log(chalk.blue('\n' + '─'.repeat(62)));
605
662
  console.log(chalk.bold.white(' Next steps:'));
@@ -623,3 +680,5 @@ async function init(options) {
623
680
 
624
681
  module.exports = init;
625
682
  module.exports.AI_TOOL_FILES = AI_TOOL_FILES;
683
+ module.exports.detectRtk = detectRtk;
684
+ module.exports.isRtkHookConfigured = isRtkHookConfigured;