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 +9 -4
- package/.agents/plugins.js +248 -23
- package/README.md +38 -3
- package/bin/index.js +157 -51
- package/bin/lib/ui.js +140 -0
- package/package.json +1 -1
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>
|
|
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
|
|
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
|
-
|
|
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 => {
|
package/.agents/plugins.js
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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
|
-
//
|
|
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 =>
|
|
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:
|
|
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>
|
|
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
|
-
|
|
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.
|
|
210
|
+
- uses: kok-o/contextos-agents/.github/actions/contextos-gate@v2.2.0
|
|
176
211
|
with:
|
|
177
|
-
version: '2.
|
|
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
|
|
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
|
|
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
|
|
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})
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
|
818
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
852
|
-
|
|
853
|
-
|
|
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
|
+
};
|