chainlesschain 0.43.2 → 0.43.4

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.
@@ -14,6 +14,12 @@ import { logger } from "../lib/logger.js";
14
14
  import { CLISkillLoader, LAYER_NAMES } from "../lib/skill-loader.js";
15
15
  import { getElectronUserDataDir } from "../lib/paths.js";
16
16
  import { findProjectRoot } from "../lib/project-detector.js";
17
+ import {
18
+ generateCliPacks,
19
+ checkForUpdates,
20
+ removeCliPacks,
21
+ } from "../lib/skill-packs/generator.js";
22
+ import { CLI_PACK_DOMAINS } from "../lib/skill-packs/schema.js";
17
23
 
18
24
  const LAYER_LABELS = {
19
25
  bundled: chalk.blue("[bundled]"),
@@ -474,6 +480,166 @@ export function registerSkillCommand(program) {
474
480
  }
475
481
  });
476
482
 
483
+ // skill sync-cli — generate / update CLI command skill packs
484
+ skill
485
+ .command("sync-cli")
486
+ .description(
487
+ "Generate or update CLI command skill packs (9 domain packs wrapping all 62 CLI commands)",
488
+ )
489
+ .option("--force", "Force regenerate all packs even if unchanged")
490
+ .option("--dry-run", "Preview changes without writing files")
491
+ .option("--remove", "Remove all generated CLI packs")
492
+ .option(
493
+ "--output <dir>",
494
+ "Custom output directory (default: managed skills layer)",
495
+ )
496
+ .option("--json", "Output result as JSON")
497
+ .action(async (options) => {
498
+ // Remove mode
499
+ if (options.remove) {
500
+ const spinner = ora("Removing CLI pack skills...").start();
501
+ try {
502
+ const removed = removeCliPacks(options.output);
503
+ spinner.stop();
504
+ if (removed.length === 0) {
505
+ logger.info("No CLI packs found to remove.");
506
+ } else {
507
+ logger.success(`Removed ${removed.length} CLI pack(s):`);
508
+ for (const d of removed) {
509
+ logger.log(` ${chalk.gray("✗")} ${d}`);
510
+ }
511
+ }
512
+ loader.clearCache();
513
+ } catch (err) {
514
+ spinner.fail(`Failed to remove packs: ${err.message}`);
515
+ process.exit(1);
516
+ }
517
+ return;
518
+ }
519
+
520
+ // Dry-run: preview what would change
521
+ if (options.dryRun) {
522
+ const updates = checkForUpdates(options.output);
523
+ const totalDomains = Object.keys(CLI_PACK_DOMAINS).length;
524
+ const upToDate = totalDomains - updates.length;
525
+
526
+ logger.log(chalk.bold("\nCLI Pack Sync Preview (dry-run):\n"));
527
+ logger.log(
528
+ chalk.gray(
529
+ ` Total domains: ${totalDomains} | Up-to-date: ${upToDate} | Need update: ${updates.length}\n`,
530
+ ),
531
+ );
532
+
533
+ if (updates.length === 0) {
534
+ logger.success("All CLI packs are up-to-date. Nothing to generate.");
535
+ return;
536
+ }
537
+
538
+ for (const u of updates) {
539
+ const icon = u.exists ? chalk.yellow("↻") : chalk.green("+");
540
+ const reason = u.changeReason === "new" ? "new" : "hash changed";
541
+ logger.log(
542
+ ` ${icon} ${chalk.cyan(u.domain.padEnd(28))} ${chalk.gray(reason)} — ${u.displayName}`,
543
+ );
544
+ }
545
+ logger.log(
546
+ chalk.gray(
547
+ `\nRun without --dry-run to generate ${updates.length} pack(s).\n`,
548
+ ),
549
+ );
550
+ return;
551
+ }
552
+
553
+ // Generate packs
554
+ const spinner = ora(
555
+ options.force
556
+ ? "Regenerating all CLI packs (--force)..."
557
+ : "Syncing CLI command skill packs...",
558
+ ).start();
559
+
560
+ try {
561
+ const result = await generateCliPacks({
562
+ force: options.force || false,
563
+ dryRun: false,
564
+ outputDir: options.output,
565
+ });
566
+
567
+ spinner.stop();
568
+
569
+ if (options.json) {
570
+ console.log(JSON.stringify(result, null, 2));
571
+ return;
572
+ }
573
+
574
+ const totalDomains = Object.keys(CLI_PACK_DOMAINS).length;
575
+
576
+ logger.log(chalk.bold("\nCLI Pack Sync Result:\n"));
577
+ logger.log(
578
+ chalk.gray(
579
+ ` CLI version: ${result.cliVersion} | Output: ${result.outputDir}\n`,
580
+ ),
581
+ );
582
+
583
+ if (result.generated.length > 0) {
584
+ logger.log(
585
+ chalk.green(` Generated / Updated (${result.generated.length}):`),
586
+ );
587
+ for (const g of result.generated) {
588
+ const icon =
589
+ g.changeReason === "new" ? chalk.green("+") : chalk.yellow("↻");
590
+ const mode = chalk.gray(`[${g.executionMode || "direct"}]`);
591
+ const cmds = chalk.gray(`${g.commandCount || 0} commands`);
592
+ logger.log(
593
+ ` ${icon} ${chalk.cyan((g.domain || g).padEnd(28))} ${mode} ${cmds}`,
594
+ );
595
+ }
596
+ logger.log("");
597
+ }
598
+
599
+ if (result.skipped.length > 0) {
600
+ logger.log(chalk.gray(` Skipped (${result.skipped.length}):`));
601
+ for (const s of result.skipped) {
602
+ logger.log(
603
+ ` ${chalk.gray("–")} ${chalk.gray((s.domain || s).padEnd(28))} up-to-date`,
604
+ );
605
+ }
606
+ logger.log("");
607
+ }
608
+
609
+ if (result.errors.length > 0) {
610
+ logger.log(chalk.red(` Errors (${result.errors.length}):`));
611
+ for (const e of result.errors) {
612
+ logger.log(` ${chalk.red("✗")} ${e.domain}: ${e.error}`);
613
+ }
614
+ logger.log("");
615
+ process.exit(1);
616
+ }
617
+
618
+ const summary =
619
+ result.generated.length > 0
620
+ ? `${result.generated.length} pack(s) generated, ${result.skipped.length} skipped`
621
+ : "All packs up-to-date";
622
+
623
+ logger.success(
624
+ `${summary} (${totalDomains} total domains, outputDir: ${result.outputDir})`,
625
+ );
626
+ logger.log(
627
+ chalk.gray(
628
+ `\nRun ${chalk.cyan("chainlesschain skill list --category cli-direct")} to see the packs.\n`,
629
+ ),
630
+ );
631
+
632
+ // Invalidate loader cache so new packs are visible immediately
633
+ loader.clearCache();
634
+ } catch (err) {
635
+ spinner.fail(`Sync failed: ${err.message}`);
636
+ if (program.opts().verbose) {
637
+ console.error(err.stack);
638
+ }
639
+ process.exit(1);
640
+ }
641
+ });
642
+
477
643
  // skill sources — show layer paths and counts
478
644
  skill
479
645
  .command("sources")
@@ -218,6 +218,10 @@ export class CLISkillLoader {
218
218
  handler: data.handler || null,
219
219
  capabilities: data.capabilities || [],
220
220
  os: data.os || [],
221
+ // CLI pack extended fields
222
+ executionMode: data.executionMode || null,
223
+ cliDomain: data.cliDomain || null,
224
+ cliVersionHash: data.cliVersionHash || null,
221
225
  dirName: entry.name,
222
226
  hasHandler: fs.existsSync(path.join(dir, entry.name, "handler.js")),
223
227
  body,