contextos-agents 2.1.1 → 2.2.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/.agents/ctx.js CHANGED
@@ -55,10 +55,13 @@ function printHelp() {
55
55
  console.log(' watch Start continuous file watcher and auto-sync daemon');
56
56
  console.log(' init Show initialization guide');
57
57
  console.log(' install-skill <ref> Alias for skill add (install a plugin)');
58
- console.log(' skill add <ref> Install a plugin skill (GitHub or npm)');
58
+ console.log(' skill add <ref|--all> Install a plugin skill or all catalog skills (--all)');
59
59
  console.log(' skill remove <name> Uninstall a plugin skill');
60
- console.log(' skill list List installed skills (builtin + plugins)');
60
+ console.log(' skill list [--available] List installed skills (and available catalog skills)');
61
61
  console.log(' skill search [query] Search the community skill registry');
62
+ console.log(' skill override <name> Create editable project copy of an upstream skill');
63
+ console.log(' skill diff <name> Show differences between local override and upstream');
64
+ console.log(' skill eject <name> Eject upstream skill to standalone project skill');
62
65
  console.log('');
63
66
  console.log('Plugin ref formats:');
64
67
  console.log(' username/repo GitHub repo root SKILL.md');
@@ -634,15 +637,17 @@ if (command === 'export') {
634
637
  }
635
638
 
636
639
  if (subcommand === 'add') {
640
+ const isAll = args.includes('--all') || ref === '--all' || ref === 'all';
637
641
  const forceUnsafe = args.includes('--force-unsafe-prompts') || args.includes('--force-unsafe');
638
- plugins.add(ref, { dryRun, checksum, forceUnsafe }).catch(err => {
642
+ plugins.add(isAll ? '--all' : ref, { dryRun, checksum, forceUnsafe, all: isAll }).catch(err => {
639
643
  console.error(`[ERROR] ${err.message}`);
640
644
  process.exit(1);
641
645
  });
642
646
  } else if (subcommand === 'remove') {
643
647
  plugins.remove(ref);
644
648
  } else if (subcommand === 'list') {
645
- plugins.list();
649
+ const available = args.includes('--available') || args.includes('-a') || args.includes('--all');
650
+ plugins.list({ available });
646
651
  } else if (subcommand === 'search') {
647
652
  const query = args.slice(2).join(' ');
648
653
  plugins.search(query).catch(err => {
@@ -47,6 +47,25 @@ const REGISTRY_URL = 'https://raw.githubusercontent.com/kok-o/contextos-agents
47
47
  const MAX_DOWNLOAD_BYTES = 5 * 1024 * 1024;
48
48
  const SAFE_SKILL_NAME = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/;
49
49
 
50
+ function getCatalogDir() {
51
+ if (process.env.CONTEXTOS_CATALOG_DIR && fs.existsSync(process.env.CONTEXTOS_CATALOG_DIR)) {
52
+ return process.env.CONTEXTOS_CATALOG_DIR;
53
+ }
54
+ const localAgentsCatalog = path.join(__dirname, 'catalog', 'skills');
55
+ if (fs.existsSync(localAgentsCatalog)) return localAgentsCatalog;
56
+
57
+ const relativeCatalog = path.join(path.resolve(__dirname, '..'), 'catalog', 'skills');
58
+ if (fs.existsSync(relativeCatalog)) return relativeCatalog;
59
+
60
+ try {
61
+ const pkgPath = require.resolve('contextos-agents/package.json', { paths: [__dirname, process.cwd()] });
62
+ const pkgCatalog = path.join(path.dirname(pkgPath), 'catalog', 'skills');
63
+ if (fs.existsSync(pkgCatalog)) return pkgCatalog;
64
+ } catch {}
65
+
66
+ return null;
67
+ }
68
+
50
69
  // ── ANSI helpers ──────────────────────────────────────────────────────────────
51
70
  const NO_COLOR = process.env.NO_COLOR || !process.stdout.isTTY;
52
71
  const c = {
@@ -291,7 +310,7 @@ function parseRef(ref, projectRoot = null) {
291
310
 
292
311
  // 1. Built-in package catalog skills take strict precedence for catalog names.
293
312
  // Consumer project local files can never spoof or hijack package catalog skills.
294
- const pkgCatalogPath = path.join(path.resolve(__dirname, '..'), 'catalog', 'skills', ref);
313
+ const pkgCatalogPath = path.join(getCatalogDir(), ref);
295
314
  if (fs.existsSync(pkgCatalogPath) && fs.existsSync(path.join(pkgCatalogPath, 'SKILL.md'))) {
296
315
  return {
297
316
  type: 'catalog',
@@ -636,9 +655,137 @@ function isSafeSkillName(skillName) {
636
655
  return typeof skillName === 'string' && SAFE_SKILL_NAME.test(skillName);
637
656
  }
638
657
 
639
- // ═════════════════════════════════════════════════════════════════════════════
640
- // COMMANDS
641
- // ═════════════════════════════════════════════════════════════════════════════
658
+ function findClosestSkill(target, candidates) {
659
+ let closest = null;
660
+ let minDistance = Infinity;
661
+ for (const item of candidates) {
662
+ if (item === target) return item;
663
+ if (item.includes(target) || target.includes(item)) return item;
664
+ const dist = levenshtein(target, item);
665
+ if (dist < minDistance && dist <= 3) {
666
+ minDistance = dist;
667
+ closest = item;
668
+ }
669
+ }
670
+ return closest;
671
+ }
672
+
673
+ function levenshtein(a, b) {
674
+ const m = a.length, n = b.length;
675
+ const d = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
676
+ for (let i = 0; i <= m; i++) d[i][0] = i;
677
+ for (let j = 0; j <= n; j++) d[0][j] = j;
678
+ for (let i = 1; i <= m; i++) {
679
+ for (let j = 1; j <= n; j++) {
680
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
681
+ d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
682
+ }
683
+ }
684
+ return d[m][n];
685
+ }
686
+
687
+ /**
688
+ * Batch install all available catalog skills.
689
+ */
690
+ async function addAllFromCatalog(options = {}) {
691
+ const dryRun = Boolean(options.dryRun || options['dry-run']);
692
+ const force = Boolean(options.force);
693
+ const catalogDir = getCatalogDir();
694
+ if (!fs.existsSync(catalogDir)) {
695
+ throw new Error('Catalog directory not found in package');
696
+ }
697
+
698
+ const catalogSkills = fs.readdirSync(catalogDir).filter(n => {
699
+ const full = path.join(catalogDir, n);
700
+ return fs.statSync(full).isDirectory() && fs.existsSync(path.join(full, 'SKILL.md'));
701
+ });
702
+
703
+ console.log('');
704
+ console.log(c.bold(`Batch installing ${catalogSkills.length} catalog skills...`));
705
+
706
+ const lock = readLock();
707
+ const existingNames = new Set(lock.plugins.map(p => p.name));
708
+
709
+ const projectLock = new ProjectMutationLock(ROOT);
710
+ const tx = new JournaledTransaction(ROOT);
711
+ let lockToken = null;
712
+
713
+ let installedCount = 0;
714
+ let skippedCount = 0;
715
+ const newlyInstalled = [];
716
+
717
+ try {
718
+ lockToken = projectLock.acquire({ command: 'skill:add-all' });
719
+
720
+ for (const skillName of catalogSkills) {
721
+ if (existingNames.has(skillName) && !force) {
722
+ skippedCount++;
723
+ continue;
724
+ }
725
+
726
+ const descriptor = {
727
+ type: 'catalog',
728
+ name: skillName,
729
+ path: path.join(catalogDir, skillName),
730
+ isPinned: true,
731
+ raw: skillName,
732
+ };
733
+
734
+ const installedSha256 = installFromCatalog(descriptor, skillName, dryRun, null, false, tx);
735
+
736
+ if (!dryRun) {
737
+ const idx = lock.plugins.findIndex(p => p.name === skillName);
738
+ const entry = {
739
+ name: skillName,
740
+ ref: skillName,
741
+ type: 'catalog',
742
+ sha256: installedSha256,
743
+ installedAt: new Date().toISOString(),
744
+ };
745
+ if (idx >= 0) lock.plugins[idx] = entry;
746
+ else lock.plugins.push(entry);
747
+ newlyInstalled.push(skillName);
748
+ }
749
+ installedCount++;
750
+ }
751
+
752
+ if (!dryRun) {
753
+ writeLock(lock, tx);
754
+ tx.commit();
755
+
756
+ for (const skillName of newlyInstalled) {
757
+ const targetDir = path.join(PLUGINS_DIR, skillName);
758
+ if (fs.existsSync(targetDir)) {
759
+ const digestResult = calculateTreeDigest(targetDir);
760
+ const entry = lock.plugins.find(p => p.name === skillName);
761
+ if (entry) entry.treeDigest = digestResult.treeDigest;
762
+ }
763
+ }
764
+ writeLock(lock);
765
+ }
766
+
767
+ console.log('');
768
+ if (dryRun) {
769
+ console.log(c.dim(`[DRY-RUN] Would install ${installedCount} skills (${skippedCount} already installed - skipped).`));
770
+ } else {
771
+ console.log(c.green(c.bold(`✓ Batch install complete: ${installedCount} catalog skills installed.`)));
772
+ if (skippedCount > 0) {
773
+ console.log(c.dim(` (${skippedCount} skills already installed - skipped)`));
774
+ }
775
+ console.log('');
776
+ console.log(' Next steps:');
777
+ console.log(' node .agents/ctx.js export all # recompile with new skills');
778
+ console.log(' node .agents/ctx.js validate # verify consistency');
779
+ }
780
+
781
+ return { installedCount, skippedCount };
782
+ } catch (err) {
783
+ console.error(c.red(`\n[ERROR] Batch installation failed: ${err.message}`));
784
+ process.exit(1);
785
+ } finally {
786
+ if (lockToken) projectLock.release(lockToken);
787
+ }
788
+ }
642
789
 
643
790
  /**
644
791
  * skill add <ref> [--dry-run] [--checksum <sha256>] [--force-unsafe-prompts]
@@ -650,6 +797,10 @@ async function add(ref, options = {}) {
650
797
  const allowFloating = Boolean(options.allowFloating || options['allow-floating']);
651
798
  const force = Boolean(options.force);
652
799
 
800
+ if (ref === '--all' || ref === 'all' || options.all) {
801
+ return await addAllFromCatalog(options);
802
+ }
803
+
653
804
  if (!ref) {
654
805
  console.error(c.red('Usage: ctx.js skill add <ref> [--checksum <sha256>]'));
655
806
  console.error(' ref can be: username/repo, username/repo/path/to/skill, or npm-package-name');
@@ -663,7 +814,37 @@ async function add(ref, options = {}) {
663
814
  throw new Error(`Invalid plugin name derived from reference: '${skillName}'`);
664
815
  }
665
816
 
666
- // Supply-chain source pinning check (Wave 6)
817
+ // 1. Check for conflict with builtin skills first
818
+ const builtinPath = path.join(CORE_SKILLS, skillName);
819
+ if (fs.existsSync(builtinPath)) {
820
+ console.error(c.red(`\n[ERROR] '${skillName}' conflicts with a built-in skill.`));
821
+ console.error(c.red(' Built-in skills cannot be overridden via --add-skill.'));
822
+ console.error(c.dim(` To customize a built-in skill for your project, run: contextos skill override ${skillName}`));
823
+ process.exit(1);
824
+ }
825
+
826
+ // 2. If single word not in catalog, check for typo suggestion
827
+ if (descriptor.type !== 'catalog' && !ref.includes('/') && !ref.includes('@')) {
828
+ const catalogDir = getCatalogDir();
829
+ if (fs.existsSync(catalogDir)) {
830
+ const catalogSkills = fs.readdirSync(catalogDir).filter(n => {
831
+ try {
832
+ return fs.statSync(path.join(catalogDir, n)).isDirectory();
833
+ } catch {
834
+ return false;
835
+ }
836
+ });
837
+ const suggestion = findClosestSkill(skillName, catalogSkills);
838
+ if (suggestion) {
839
+ console.error(c.red(`\n[ERROR] Skill '${skillName}' not found in catalog.`));
840
+ console.log(c.yellow(` Did you mean: "${suggestion}"?`));
841
+ console.log(c.dim(' Run "contextos skill list --available" to see all catalog skills.'));
842
+ process.exit(1);
843
+ }
844
+ }
845
+ }
846
+
847
+ // 3. Supply-chain source pinning check (Wave 6)
667
848
  if (!dryRun && descriptor.type !== 'catalog') {
668
849
  const pinning = validatePluginPinning(
669
850
  {
@@ -688,15 +869,6 @@ async function add(ref, options = {}) {
688
869
  console.log(c.bold(`Installing skill: ${c.cyan(skillName)}`));
689
870
  console.log(c.dim(` Source: ${ref} (${descriptor.type})`));
690
871
 
691
- // Check for conflict with builtin skills
692
- const builtinPath = path.join(CORE_SKILLS, skillName);
693
- if (fs.existsSync(builtinPath)) {
694
- console.error(c.red(`\n[ERROR] '${skillName}' conflicts with a built-in skill.`));
695
- console.error(c.red(' Built-in skills cannot be overridden via --add-skill.'));
696
- console.error(c.dim(' To customize a built-in skill, fork the repo and submit a PR.'));
697
- process.exit(1);
698
- }
699
-
700
872
  // Check if already installed
701
873
  const lock = readLock();
702
874
  const existing = lock.plugins.find(p => p.name === skillName);
@@ -779,6 +951,21 @@ async function add(ref, options = {}) {
779
951
  // We don't need to do anything since it wasn't committed
780
952
  }
781
953
  console.error(c.red(`\n[ERROR] Failed to install '${skillName}': ${err.message}`));
954
+ const catalogDir = getCatalogDir();
955
+ if (fs.existsSync(catalogDir)) {
956
+ const catalogSkills = fs.readdirSync(catalogDir).filter(n => {
957
+ try {
958
+ return fs.statSync(path.join(catalogDir, n)).isDirectory();
959
+ } catch {
960
+ return false;
961
+ }
962
+ });
963
+ const suggestion = findClosestSkill(skillName, catalogSkills);
964
+ if (suggestion) {
965
+ console.log(c.yellow(` Did you mean: "${suggestion}"?`));
966
+ console.log(c.dim(' Run "contextos skill list --available" to see all catalog skills.'));
967
+ }
968
+ }
782
969
  process.exit(1);
783
970
  } finally {
784
971
  if (lockToken) projectLock.release(lockToken);
@@ -898,25 +1085,46 @@ function remove(skillName) {
898
1085
  }
899
1086
 
900
1087
  /**
901
- * skill list
1088
+ * skill list [options]
902
1089
  */
903
- function list() {
1090
+ function list(options = {}) {
1091
+ const showAvailable = Boolean(options.available || options['--available'] || options.all);
904
1092
  const lock = readLock();
905
1093
 
906
1094
  // Builtin skills
907
1095
  const builtins = fs.existsSync(CORE_SKILLS)
908
- ? fs.readdirSync(CORE_SKILLS).filter(n => fs.statSync(path.join(CORE_SKILLS, n)).isDirectory())
1096
+ ? fs.readdirSync(CORE_SKILLS).filter(n => {
1097
+ try {
1098
+ return fs.statSync(path.join(CORE_SKILLS, n)).isDirectory();
1099
+ } catch {
1100
+ return false;
1101
+ }
1102
+ })
909
1103
  : [];
910
1104
 
911
1105
  // Plugin skills
912
1106
  const plugins = lock.plugins;
913
1107
 
1108
+ // Catalog skills
1109
+ const catalogDir = getCatalogDir();
1110
+ const catalogSkills = fs.existsSync(catalogDir)
1111
+ ? fs.readdirSync(catalogDir).filter(n => {
1112
+ try {
1113
+ return fs.statSync(path.join(catalogDir, n)).isDirectory();
1114
+ } catch {
1115
+ return false;
1116
+ }
1117
+ })
1118
+ : [];
1119
+ const installedNames = new Set(plugins.map(p => p.name));
1120
+ const availableCatalog = catalogSkills.filter(n => !installedNames.has(n));
1121
+
914
1122
  console.log('');
915
1123
  console.log(c.bold('ContextOS Skills'));
916
1124
  console.log('');
917
1125
 
918
1126
  // Built-ins
919
- console.log(c.bold(` Built-in (${builtins.length})`));
1127
+ console.log(c.bold(` Built-in Core (${builtins.length})`));
920
1128
  for (const name of builtins) {
921
1129
  console.log(` ${c.cyan('●')} ${name} ${c.dim('(core)')}`);
922
1130
  }
@@ -924,22 +1132,39 @@ function list() {
924
1132
  // Plugins
925
1133
  console.log('');
926
1134
  if (plugins.length === 0) {
927
- console.log(c.bold(' Plugins (0)'));
928
- console.log(c.dim(' No plugins installed yet.'));
929
- console.log(c.dim(' Install one: node .agents/ctx.js skill add username/my-skill'));
1135
+ console.log(c.bold(' Installed Plugins & Domain Skills (0)'));
1136
+ console.log(c.dim(' No domain skills or plugins installed yet.'));
1137
+ console.log(c.dim(' Install one: contextos skill add <name> (or --all)'));
930
1138
  } else {
931
- console.log(c.bold(` Plugins (${plugins.length})`));
1139
+ console.log(c.bold(` Installed Plugins & Domain Skills (${plugins.length})`));
932
1140
  for (const p of plugins) {
933
1141
  const when = p.installedAt ? new Date(p.installedAt).toLocaleDateString() : '?';
934
1142
  console.log(` ${c.green('●')} ${p.name} ${c.dim(`← ${p.ref} (installed ${when})`)}`);
935
1143
  }
936
1144
  }
937
1145
 
1146
+ // Available in Catalog
1147
+ console.log('');
1148
+ if (showAvailable || availableCatalog.length > 0) {
1149
+ console.log(c.bold(` Available in Catalog (${availableCatalog.length})`));
1150
+ if (showAvailable) {
1151
+ for (const name of availableCatalog) {
1152
+ console.log(` ${c.dim('○')} ${name}`);
1153
+ }
1154
+ console.log('');
1155
+ console.log(c.dim(' Install with: contextos skill add <name> (or contextos skill add --all)'));
1156
+ } else {
1157
+ console.log(c.dim(` ${availableCatalog.length} domain skills available in local catalog.`));
1158
+ console.log(c.dim(' Run "contextos skill list --available" to see all, or install with:'));
1159
+ console.log(c.dim(' contextos skill add <name> | contextos skill add --all'));
1160
+ }
1161
+ }
1162
+
938
1163
  console.log('');
939
1164
  }
940
1165
 
941
1166
  /**
942
- * skill search <query> — searches the hosted registry.json
1167
+ * skill search <query> - searches the hosted registry.json
943
1168
  */
944
1169
  async function search(query) {
945
1170
  console.log(c.dim(`\nFetching registry from: ${REGISTRY_URL}\n`));
package/README.md CHANGED
@@ -40,7 +40,16 @@ You do not need to clone anything manually. Just open your terminal in the root
40
40
  npx contextos-agents init
41
41
  ```
42
42
 
43
- The script will automatically detect your project tech stack, create the `.agents` folder, configure a neutral bootstrap profile, and compile it for your AI agent.
43
+ By default, ContextOS sets up a focused **Lean Core** (7 essential skills: `engineering-workflow`, `ponytail-mindset`, `gemini-precision`, `security`, `context-os`, `context-manager`, and `gstack-roles`). This ensures your AI agent context remains lightweight and focused, preventing context degradation.
44
+
45
+ Want more skills right away? Install pre-packaged presets or the entire catalog:
46
+
47
+ ```bash
48
+ npx contextos-agents init --preset frontend # React, Next.js, TypeScript, UI/UX, a11y
49
+ npx contextos-agents init --preset backend # System design, API design, Node.js, databases
50
+ npx contextos-agents init --preset devops # Docker, CI/CD, Terraform
51
+ npx contextos-agents init --all # Install all 36 catalog skills at once
52
+ ```
44
53
 
45
54
  ### Options
46
55
 
@@ -48,6 +57,8 @@ The script will automatically detect your project tech stack, create the `.agent
48
57
  npx contextos-agents --help # Show all options
49
58
  npx contextos-agents --version # Show version
50
59
  npx contextos-agents --minimal # Install only the core bootstrap skills
60
+ npx contextos-agents --all # Install all 36 catalog domain skills during init
61
+ npx contextos-agents --preset <name> # Install stack preset: frontend, backend, devops, full
51
62
  npx contextos-agents --profile init # Install with specific profile
52
63
  npx contextos-agents --auto # Auto-detect tech stack and apply recommended profile
53
64
  npx contextos-agents --dry-run # Preview what will be installed
@@ -110,6 +121,30 @@ contextos resolve "Build an accessible modal component with React and Tailwind"
110
121
  contextos resolve "security review" --files apps/web/app/login/page.tsx --explain
111
122
  ```
112
123
 
124
+ ### Skill & Catalog Management (`contextos skill`)
125
+
126
+ Discover, install, and customize skills:
127
+
128
+ ```bash
129
+ # Explore all available catalog skills (36 domain skills):
130
+ contextos skill list --available
131
+
132
+ # Install a specific skill from the catalog (with typo suggestions):
133
+ contextos skill add fastapi
134
+
135
+ # Install all 36 catalog skills at once:
136
+ contextos skill add --all
137
+
138
+ # Fork a built-in skill into your project for team customizations:
139
+ contextos skill override gemini-precision
140
+
141
+ # Diff your local customizations against upstream updates:
142
+ contextos skill diff gemini-precision
143
+
144
+ # Eject a skill to decouple it from upstream updates:
145
+ contextos skill eject gemini-precision
146
+ ```
147
+
113
148
  ### Diagnostic Health Check (`contextos doctor`)
114
149
 
115
150
  Run a comprehensive pre-flight verification across your repository to ensure valid skills, profile alignment, and compiler synchronization:
@@ -172,9 +207,9 @@ jobs:
172
207
  runs-on: ubuntu-latest
173
208
  steps:
174
209
  - uses: actions/checkout@v4
175
- - uses: kok-o/contextos-agents/.github/actions/contextos-gate@v2.1.1
210
+ - uses: kok-o/contextos-agents/.github/actions/contextos-gate@v2.2.0
176
211
  with:
177
- version: '2.1.1' # Pinned version of contextos-agents runner
212
+ version: '2.2.0' # Pinned version of contextos-agents runner
178
213
  adapters: 'all' # Adapters to verify (or specific: 'cursor', 'claude')
179
214
  working-directory: '.' # Project root directory
180
215
  ```
package/bin/index.js CHANGED
@@ -7,6 +7,7 @@ const profiles = require('../.agents/profiles.js');
7
7
  const { isKnownCommand, getStatus, formatStatusText } = require('./commands.js');
8
8
  const { detectProjectAttributes } = require('./lib/detector.js');
9
9
  const lockfileLib = require('./lib/lockfile.js');
10
+ const ui = require('./lib/ui.js');
10
11
 
11
12
  // ── CLI argument parsing ──────────────────────────────────────────────────────
12
13
  const args = process.argv.slice(2);
@@ -41,6 +42,27 @@ const flags = {
41
42
  return i !== -1 && args[i + 1] && !args[i + 1].startsWith('-') ? args[i + 1] : null;
42
43
  })(),
43
44
  githubAnnotations: args.includes('--github-annotations'),
45
+ all: args.includes('--all') || args.includes('--full'),
46
+ preset: (() => {
47
+ const i = args.indexOf('--preset');
48
+ return i !== -1 && args[i + 1] && !args[i + 1].startsWith('-') ? args[i + 1].toLowerCase() : null;
49
+ })(),
50
+ };
51
+
52
+ const PRESETS = {
53
+ frontend: [
54
+ 'react', 'react-best-practices', 'nextjs', 'typescript',
55
+ 'ui-ux-pro', 'impeccable-design', 'state-management', 'web-accessibility'
56
+ ],
57
+ backend: [
58
+ 'node', 'fastapi', 'nestjs', 'system-design',
59
+ 'api-design', 'microservices', 'ddd', 'database'
60
+ ],
61
+ devops: [
62
+ 'docker', 'ci-cd', 'terraform', 'security-audit'
63
+ ],
64
+ full: null,
65
+ all: null,
44
66
  };
45
67
 
46
68
  // ── Help / Version ────────────────────────────────────────────────────────────
@@ -71,6 +93,8 @@ Options:
71
93
  --dry-run Preview what will be copied without making changes
72
94
  --force Overwrite an existing .agents/ folder
73
95
  --minimal Install only 7 core skills (lightweight footprint)
96
+ --all, --full Install all 36 catalog skills alongside 7 core skills
97
+ --preset <name> Install domain preset (frontend, backend, devops, full)
74
98
  --profile <name> Install a specific profile (mvp, startup, enterprise, frontend, backend)
75
99
  --auto Auto-detect tech stack and apply recommended profile
76
100
  --project <path> Specify target project root directory (default: current directory)
@@ -82,6 +106,7 @@ Options:
82
106
 
83
107
  Commands:
84
108
  init Install and configure .agents/ in target project
109
+ skill <subcmd> Manage skills (add, remove, list, search, override, diff, eject)
85
110
  gate Run deterministic quality gate against adapter drift
86
111
  status Display project configuration, active profile, and lockfile status
87
112
  update Safely update skills without overwriting custom changes
@@ -119,9 +144,14 @@ Plugin ref formats:
119
144
  @scope/npm-package A scoped npm skill package
120
145
 
121
146
  Examples:
122
- npx contextos-agents init Install .agents/ with auto-detected stack
147
+ npx contextos-agents init Install Lean Core (7 foundational skills)
148
+ npx contextos-agents init --all Install ContextOS with all 43 skills
149
+ npx contextos-agents init --preset frontend Install ContextOS with frontend domain skills
123
150
  npx contextos-agents init --agent auto Install and configure for detected stack & IDE
124
- npx contextos-agents init --minimal Install only 7 core essential skills
151
+ npx contextos skill add <name> Install a domain skill from catalog
152
+ npx contextos skill add --all Install all catalog skills into existing project
153
+ npx contextos skill list --available List all available catalog domain skills
154
+ npx contextos skill override <name> Create editable local copy of a skill
125
155
  npx contextos status Show project configuration and lockfile status
126
156
  npx contextos doctor Run project health check
127
157
  npx contextos export gemini Compile skills for Gemini
@@ -178,7 +208,7 @@ if (mainCommand === 'detect') {
178
208
  }, null, 2));
179
209
  } else {
180
210
  console.log('\n══════════════════════════════════════════');
181
- console.log(' ContextOS — Workspace Scoped Detection');
211
+ console.log(' ContextOS - Workspace Scoped Detection');
182
212
  console.log('══════════════════════════════════════════\n');
183
213
  console.log(` Scope Path : ${scopeArg}`);
184
214
  if (nearestPkg) {
@@ -193,7 +223,7 @@ if (mainCommand === 'detect') {
193
223
  if (evidence.length > 0) {
194
224
  console.log('\n Scoped Evidence:');
195
225
  for (const ev of evidence.slice(0, 10)) {
196
- console.log(` • [${ev.source}] ${ev.target} (+${ev.weight}) — ${ev.description}`);
226
+ console.log(` • [${ev.source}] ${ev.target} (+${ev.weight}) - ${ev.description}`);
197
227
  }
198
228
  if (evidence.length > 10) {
199
229
  console.log(` ... and ${evidence.length - 10} more signals`);
@@ -212,7 +242,7 @@ if (mainCommand === 'detect') {
212
242
  }, null, 2));
213
243
  } else if (explain) {
214
244
  console.log('\n══════════════════════════════════════════');
215
- console.log(' ContextOS — Workspace Evidence Graph');
245
+ console.log(' ContextOS - Workspace Evidence Graph');
216
246
  console.log('══════════════════════════════════════════\n');
217
247
  console.log(` Repository Root : ${graph.repositoryRoot}`);
218
248
  console.log(` Fingerprint : ${graph.fingerprint}`);
@@ -234,7 +264,7 @@ if (mainCommand === 'detect') {
234
264
  console.log('──────────────────────────────────────────\n');
235
265
  } else {
236
266
  const detection = detectProjectAttributes(process.cwd());
237
- console.log('\nContextOS — Tech Stack & Environment Detection\n');
267
+ console.log('\nContextOS - Tech Stack & Environment Detection\n');
238
268
  console.log(` Repository Root : ${graph.repositoryRoot}`);
239
269
  console.log(` Packages Detected : ${graph.packages.map(p => `${p.id} (${p.root})`).join(', ')}`);
240
270
  console.log(` Detected Technologies : ${detection.summary.technologies.join(', ')}`);
@@ -480,7 +510,32 @@ if (mainCommand === 'install-skill') {
480
510
  }).on('error', reject);
481
511
  });
482
512
 
483
- const registry = await fetchRegistry();
513
+ let registry;
514
+ try {
515
+ registry = await fetchRegistry();
516
+ } catch {
517
+ const catalogDir = path.join(__dirname, '..', 'catalog', 'skills');
518
+ if (fs.existsSync(catalogDir)) {
519
+ const catalogSkills = fs.readdirSync(catalogDir).filter(n => {
520
+ try {
521
+ return fs.statSync(path.join(catalogDir, n)).isDirectory();
522
+ } catch {
523
+ return false;
524
+ }
525
+ });
526
+ registry = {
527
+ skills: catalogSkills.map(name => ({
528
+ name,
529
+ description: `Domain catalog skill: ${name}`,
530
+ github: null,
531
+ npm: name,
532
+ })),
533
+ };
534
+ } else {
535
+ console.error('[ERROR] Could not fetch remote registry and local catalog is unavailable.');
536
+ process.exit(1);
537
+ }
538
+ }
484
539
  const skillsList = (registry.skills || []).map((s, idx) => ({
485
540
  idx: idx + 1,
486
541
  name: s.name,
@@ -606,6 +661,13 @@ function installAtomically(source, target, options = {}) {
606
661
  return true;
607
662
  },
608
663
  });
664
+
665
+ const catalogSrc = path.join(__dirname, '..', 'catalog');
666
+ const catalogDest = path.join(stagingPath, 'catalog');
667
+ if (fs.existsSync(catalogSrc)) {
668
+ fs.cpSync(catalogSrc, catalogDest, { recursive: true, force: true });
669
+ }
670
+
609
671
  if (fs.existsSync(target)) {
610
672
  fs.renameSync(target, backupPath);
611
673
  movedExisting = true;
@@ -686,7 +748,7 @@ if (flags.dryRun) {
686
748
 
687
749
  console.log('[DRY-RUN] No files will be written.\n');
688
750
  if (fs.existsSync(targetPath)) {
689
- console.log(`[WARN] .agents/ already exists — would be overwritten with --force.`);
751
+ console.log(`[WARN] .agents/ already exists - would be overwritten with --force.`);
690
752
  } else {
691
753
  console.log(`[OK] Would create .agents/ in: ${process.cwd()}`);
692
754
  }
@@ -706,7 +768,13 @@ if (flags.dryRun) {
706
768
 
707
769
  // ── Main install ──────────────────────────────────────────────────────────────
708
770
  if (!flags.json) {
709
- console.log('Installing AI assistant skills (.agents/)...');
771
+ ui.renderBanner(version);
772
+ ui.renderStep(1, 4, 'Analyzing project environment & tech stack',
773
+ stackDetection.detected.length > 0
774
+ ? `Detected stack: ${stackDetection.detected.join(', ')}`
775
+ : 'Detected stack: Generic JavaScript environment'
776
+ );
777
+ console.log('');
710
778
  }
711
779
 
712
780
  try {
@@ -746,18 +814,14 @@ try {
746
814
  }
747
815
 
748
816
  if (!flags.json) {
749
- console.log('[OK] .agents/ successfully installed in your project!');
750
- if (flags.minimal) {
751
- console.log('[OK] Minimal profile: 5 core skills installed.');
752
- console.log(' (engineering-workflow, ponytail-mindset, gstack-roles, gemini-precision, react)');
753
- console.log(' Tip: Add more skills anytime with: contextos skill add <name>');
754
- }
755
- if (stackDetection.detected.length > 0) {
756
- console.log(`[OK] Detected project stack: ${stackDetection.detected.join(', ')}`);
757
- }
758
- if (ideDetection.detected.length > 0) {
759
- console.log(`[OK] Detected developer environment: ${ideDetection.detected.join(', ')}`);
760
- }
817
+ ui.renderStep(2, 4, 'Deploying Lean Core engine (.agents/)', [
818
+ 'Installed 7 foundational core skills in .agents/core/skills/',
819
+ 'Why 7? ContextOS uses a Lean Core to keep your AI context small, fast, and token-efficient.',
820
+ 'To add domain skills (React, FastAPI, Docker, etc.): npx contextos skill add <name> (or --all)',
821
+ ]);
822
+ console.log('');
823
+ ui.renderStep(3, 4, 'Generating lockfile & security governance', 'contextos.lock.json synchronized');
824
+ console.log('');
761
825
  }
762
826
 
763
827
  // ── Deprecation warning for --with-mcp ────────────────────────────────────────────────
@@ -771,7 +835,7 @@ try {
771
835
  if (selectedProfile) {
772
836
  try {
773
837
  const applied = profiles.applyProfile(selectedProfile, process.cwd());
774
- if (!flags.json) console.log(`[OK] Applied profile '${applied.name}' (excluded: ${(applied.exclude_skills || []).join(', ') || 'none'})`);
838
+ if (!flags.json) console.log(` [OK] Applied profile '${applied.name}' (excluded: ${(applied.exclude_skills || []).join(', ') || 'none'})\n`);
775
839
  } catch (e) {
776
840
  if (flags.json) {
777
841
  console.error(JSON.stringify({ success: false, error: `Could not apply profile '${selectedProfile}': ${e.message}` }));
@@ -782,58 +846,112 @@ try {
782
846
  }
783
847
  }
784
848
 
785
- if (!flags.json) {
786
- console.log('[OK] Your AI assistant now has skills and rules configured.\n');
787
- }
788
-
789
849
  // ── Auto-compile skills for target agents ──────────────────────────────────
790
850
  if (!flags.skipCompile) {
791
851
  const ctxPath = path.join(targetPath, 'ctx.js');
792
852
  if (fs.existsSync(ctxPath)) {
853
+ if (!flags.json) {
854
+ ui.renderStep(4, 4, 'Configuring AI assistants & compiling rules', `${targetAgents.join(', ')} synchronized`);
855
+ console.log('');
856
+ }
793
857
  const { execFileSync } = require('child_process');
794
858
  for (const ag of targetAgents) {
795
- if (!flags.json) console.log(`Compiling skills for ${ag}...`);
796
859
  try {
797
860
  execFileSync(process.execPath, [ctxPath, 'export', ag], {
798
861
  cwd: process.cwd(),
799
- stdio: flags.json ? 'ignore' : 'inherit',
862
+ stdio: 'ignore',
800
863
  });
801
864
  } catch (e) {
802
865
  if (flags.json) {
803
866
  console.error(JSON.stringify({ success: false, error: `Skill compilation for '${ag}' failed` }));
804
867
  } else {
805
- console.error(`[ERROR] Skill compilation for '${ag}' failed — run manually: contextos export ${ag}`);
868
+ console.error(` [ERROR] Skill compilation for '${ag}' failed - run manually: contextos export ${ag}`);
806
869
  }
807
870
  process.exit(1);
808
871
  }
809
872
  }
810
873
  }
811
874
  } else if (!flags.json) {
812
- console.log('Tip: Run `contextos export gemini` to compile skills.');
875
+ ui.renderStep(4, 4, 'Configuring AI assistants & compiling rules', [
876
+ 'Skipped auto-compilation (--skip-compile)',
877
+ 'Tip: Run `contextos export gemini` to compile skills when ready.',
878
+ ]);
879
+ console.log('');
813
880
  }
814
881
 
815
882
  // ── --add-skill flag ────────────────────────────────────────────────────────
816
883
  if (flags.addSkill) {
817
- const ctxPath = path.join(targetPath, 'ctx.js');
818
- if (fs.existsSync(ctxPath)) {
884
+ const packageCtxPath = path.join(__dirname, '..', '.agents', 'ctx.js');
885
+ const ctxScript = fs.existsSync(packageCtxPath) ? packageCtxPath : path.join(targetPath, 'ctx.js');
886
+ if (fs.existsSync(ctxScript)) {
819
887
  if (!flags.json) console.log(`Installing plugin skill: ${flags.addSkill}`);
820
888
  try {
821
889
  const { execFileSync } = require('child_process');
822
- execFileSync(process.execPath, [ctxPath, 'skill', 'add', flags.addSkill], {
890
+ const catalogDir = path.join(__dirname, '..', 'catalog', 'skills');
891
+ const subEnv = { ...process.env, CONTEXTOS_CATALOG_DIR: catalogDir };
892
+ execFileSync(process.execPath, [ctxScript, 'skill', 'add', flags.addSkill], {
823
893
  cwd: process.cwd(),
824
894
  stdio: flags.json ? 'ignore' : 'inherit',
895
+ env: subEnv,
825
896
  });
826
897
  } catch (e) {
827
898
  if (flags.json) {
828
899
  console.error(JSON.stringify({ success: false, error: `Skill install failed for ${flags.addSkill}` }));
829
900
  } else {
830
- console.error(`[ERROR] Skill install failed — run manually: node .agents/ctx.js skill add ${flags.addSkill}`);
901
+ console.error(`[ERROR] Skill install failed - run manually: node .agents/ctx.js skill add ${flags.addSkill}`);
831
902
  }
832
903
  process.exit(1);
833
904
  }
834
905
  }
835
906
  }
836
907
 
908
+ // ── --all / --preset flags ──────────────────────────────────────────────────
909
+ if (flags.all || flags.preset) {
910
+ const packageCtxPath = path.join(__dirname, '..', '.agents', 'ctx.js');
911
+ const ctxScript = fs.existsSync(packageCtxPath) ? packageCtxPath : path.join(targetPath, 'ctx.js');
912
+ if (fs.existsSync(ctxScript)) {
913
+ const presetKey = flags.all ? 'full' : (flags.preset || 'full');
914
+ const { execFileSync } = require('child_process');
915
+ const catalogDir = path.join(__dirname, '..', 'catalog', 'skills');
916
+ const subEnv = { ...process.env, CONTEXTOS_CATALOG_DIR: catalogDir };
917
+
918
+ if (presetKey === 'full' || presetKey === 'all') {
919
+ if (!flags.json) console.log('\nInstalling all 36 catalog skills (--all)...');
920
+ try {
921
+ execFileSync(process.execPath, [ctxScript, 'skill', 'add', '--all'], {
922
+ cwd: process.cwd(),
923
+ stdio: flags.json ? 'ignore' : 'inherit',
924
+ env: subEnv,
925
+ });
926
+ } catch (e) {
927
+ if (!flags.json) console.error(' [WARN] Failed to install catalog skills:', e.message);
928
+ }
929
+ } else if (PRESETS[presetKey]) {
930
+ if (!flags.json) console.log(` Installing preset '${presetKey}' skills:`);
931
+ const totalSkills = PRESETS[presetKey].length;
932
+ let count = 0;
933
+ for (const skillName of PRESETS[presetKey]) {
934
+ count++;
935
+ if (!flags.json) {
936
+ ui.renderProgressBar(count, totalSkills, skillName);
937
+ }
938
+ try {
939
+ execFileSync(process.execPath, [ctxScript, 'skill', 'add', skillName], {
940
+ cwd: process.cwd(),
941
+ stdio: 'ignore',
942
+ env: subEnv,
943
+ });
944
+ } catch (e) {
945
+ if (!flags.json) console.error(`\n [WARN] Failed to install preset skill '${skillName}':`, e.message);
946
+ }
947
+ }
948
+ if (!flags.json) console.log('\n');
949
+ } else {
950
+ if (!flags.json) console.error(` [WARN] Unknown preset '${flags.preset}'. Supported presets: frontend, backend, devops, full`);
951
+ }
952
+ }
953
+ }
954
+
837
955
  if (flags.json) {
838
956
  console.log(JSON.stringify({
839
957
  success: true,
@@ -844,27 +962,15 @@ try {
844
962
  agents: targetAgents,
845
963
  withMcp: flags.withMcp,
846
964
  minimal: flags.minimal,
965
+ all: flags.all,
966
+ preset: flags.preset,
847
967
  }, null, 2));
848
968
  process.exit(0);
849
969
  }
850
970
 
851
- console.log('\n Next steps:');
852
- console.log(' 1. Open your project in your AI assistant');
853
- console.log(' 2. The assistant will automatically load .agents/AGENTS.md');
854
- console.log(' 3. Inspect project health anytime:');
855
- console.log(' contextos doctor');
856
- console.log(' 4. Switch profiles anytime:');
857
- console.log(' contextos profile list');
858
- console.log(' contextos profile apply mvp');
859
- console.log(' 5. Export to other AI tools:');
860
- console.log(' contextos export cursor → .cursorrules & .cursor/rules');
861
- console.log(' contextos export copilot → .github/copilot-instructions.md');
862
- console.log(' contextos export aider → .aider.conf.yml + CONVENTIONS.md');
863
- console.log(' 6. Add community skills (plugins):');
864
- console.log(' contextos skill add username/my-skill');
865
- console.log(' contextos skill list');
866
-
867
- console.log('');
971
+ if (!flags.json) {
972
+ ui.renderSuccessCard();
973
+ }
868
974
 
869
975
  } catch (error) {
870
976
  if (flags.json) {
package/bin/lib/ui.js ADDED
@@ -0,0 +1,140 @@
1
+ /**
2
+ * bin/lib/ui.js
3
+ * Terminal visual formatting & progress helpers for ContextOS.
4
+ * Zero external dependencies - uses standard Node.js ANSI and process streams.
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const isTTY = Boolean(process.stdout && process.stdout.isTTY && !process.env.CI && process.env.NODE_ENV !== 'test');
10
+ const noColor = Boolean(process.env.NO_COLOR || (!isTTY && !process.env.FORCE_COLOR));
11
+
12
+ const c = {
13
+ cyan: (s) => noColor ? s : `\x1b[36m${s}\x1b[0m`,
14
+ green: (s) => noColor ? s : `\x1b[32m${s}\x1b[0m`,
15
+ yellow: (s) => noColor ? s : `\x1b[33m${s}\x1b[0m`,
16
+ red: (s) => noColor ? s : `\x1b[31m${s}\x1b[0m`,
17
+ blue: (s) => noColor ? s : `\x1b[34m${s}\x1b[0m`,
18
+ magenta: (s) => noColor ? s : `\x1b[35m${s}\x1b[0m`,
19
+ dim: (s) => noColor ? s : `\x1b[2m${s}\x1b[0m`,
20
+ bold: (s) => noColor ? s : `\x1b[1m${s}\x1b[0m`,
21
+ gray: (s) => noColor ? s : `\x1b[90m${s}\x1b[0m`,
22
+ };
23
+
24
+ /**
25
+ * Render a sleek ASCII brand banner with version.
26
+ */
27
+ function renderBanner(version) {
28
+ const title = `ContextOS v${version}`;
29
+ const subtitle = 'Deterministic Context & Policy Engine for Coding Agents';
30
+ const width = Math.max(title.length, subtitle.length) + 6;
31
+ const topBorder = ` ╭${'─'.repeat(width)}╮`;
32
+ const bottomBorder = ` ╰${'─'.repeat(width)}╯`;
33
+
34
+ console.log('\n' + c.cyan(topBorder));
35
+ console.log(` ${c.cyan('│')} ${c.bold(c.cyan('ContextOS'))} ${c.dim('v' + version)}${' '.repeat(width - title.length - 3)}${c.cyan('│')}`);
36
+ console.log(` ${c.cyan('│')} ${c.gray(subtitle)}${' '.repeat(width - subtitle.length - 3)}${c.cyan('│')}`);
37
+ console.log(c.cyan(bottomBorder) + '\n');
38
+ }
39
+
40
+ /**
41
+ * Format a step badge: e.g. [1/4]
42
+ */
43
+ function formatStepBadge(step, total) {
44
+ return c.cyan(`[${step}/${total}]`);
45
+ }
46
+
47
+ /**
48
+ * Render an installation step with title and detailed item.
49
+ */
50
+ function renderStep(step, total, title, detail = null, isSuccess = true) {
51
+ const badge = formatStepBadge(step, total);
52
+ console.log(` ${badge} ${c.bold(title)}`);
53
+ if (detail) {
54
+ const symbol = isSuccess ? c.green('✓') : c.yellow('•');
55
+ if (Array.isArray(detail)) {
56
+ if (detail.length > 0) {
57
+ console.log(` ${symbol} ${detail[0]}`);
58
+ for (let i = 1; i < detail.length; i++) {
59
+ console.log(` ${c.dim(detail[i])}`);
60
+ }
61
+ }
62
+ } else {
63
+ console.log(` ${symbol} ${detail}`);
64
+ }
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Render an interactive or batch progress bar for skills/files.
70
+ */
71
+ function renderProgressBar(current, total, label = '', width = 26) {
72
+ const percent = total > 0 ? Math.min(100, Math.round((current / total) * 100)) : 100;
73
+ const filledCount = total > 0 ? Math.min(width, Math.round((current / total) * width)) : width;
74
+ const emptyCount = Math.max(0, width - filledCount);
75
+
76
+ const filledBar = '█'.repeat(filledCount);
77
+ const emptyBar = '░'.repeat(emptyCount);
78
+ const barStr = `${c.cyan(filledBar)}${c.dim(emptyBar)}`;
79
+ const progressText = `[${barStr}] ${String(percent).padStart(3)}% (${current}/${total})${label ? ' ' + c.gray(label) : ''}`;
80
+
81
+ if (isTTY) {
82
+ process.stdout.write(`\r ${progressText} `);
83
+ if (current >= total) {
84
+ process.stdout.write('\n');
85
+ }
86
+ } else {
87
+ // In CI or non-TTY test runners, log only at completion to avoid log bloat
88
+ if (current === total) {
89
+ console.log(` ${progressText}`);
90
+ }
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Render the final readiness card with next steps and commands.
96
+ */
97
+ function stripAnsi(str) {
98
+ return String(str).replace(/\x1b\[[0-9;]*m/g, '');
99
+ }
100
+
101
+ function renderCardLine(content, innerWidth = 62) {
102
+ const visible = stripAnsi(content);
103
+ const padding = Math.max(0, innerWidth - visible.length);
104
+ return ` ${c.green('│')} ${content}${' '.repeat(padding)}${c.green('│')}`;
105
+ }
106
+
107
+ /**
108
+ * Render the final readiness card with next steps and commands.
109
+ */
110
+ function renderSuccessCard(options = {}) {
111
+ const innerWidth = 62;
112
+ const topBorder = ` ╭${'─'.repeat(innerWidth + 2)}╮`;
113
+ const bottomBorder = ` ╰${'─'.repeat(innerWidth + 2)}╯`;
114
+
115
+ console.log('\n' + c.green(topBorder));
116
+ console.log(renderCardLine(c.bold(c.green('✓ Project ready for AI coding agents!')), innerWidth));
117
+ console.log(renderCardLine('', innerWidth));
118
+ console.log(renderCardLine(c.bold('Next steps:'), innerWidth));
119
+ console.log(renderCardLine(`1. Open project in your AI assistant (${c.cyan('Gemini')}, ${c.cyan('Cursor')}, ${c.cyan('Copilot')})`, innerWidth));
120
+ console.log(renderCardLine(` Rules are auto-loaded from ${c.cyan('.agents/AGENTS.md')}`, innerWidth));
121
+ console.log(renderCardLine('', innerWidth));
122
+ console.log(renderCardLine('2. Explore and install domain skills:', innerWidth));
123
+ console.log(renderCardLine(` ${c.cyan('contextos skill list --available')}`, innerWidth));
124
+ console.log(renderCardLine(` ${c.cyan('contextos skill add <name>')} (or ${c.cyan('--all')})`, innerWidth));
125
+ console.log(renderCardLine('', innerWidth));
126
+ console.log(renderCardLine('3. Inspect project health anytime:', innerWidth));
127
+ console.log(renderCardLine(` ${c.cyan('contextos doctor')}`, innerWidth));
128
+ console.log(c.green(bottomBorder) + '\n');
129
+ }
130
+
131
+ module.exports = {
132
+ isTTY,
133
+ noColor,
134
+ c,
135
+ renderBanner,
136
+ formatStepBadge,
137
+ renderStep,
138
+ renderProgressBar,
139
+ renderSuccessCard,
140
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "contextos-agents",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "description": "Deterministic context and policy compiler for supported AI coding agents.",
5
5
  "bin": {
6
6
  "contextos": "./bin/index.js",