multimodel-dev-os 2.8.0 → 2.8.1
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/README.md +1 -1
- package/bin/multimodel-dev-os.js +113 -32
- package/docs/.vitepress/config.js +1 -1
- package/docs/dashboard.md +9 -7
- package/docs/faq.md +1 -1
- package/docs/plugin-authoring.md +1 -1
- package/docs/plugin-hooks.md +9 -0
- package/docs/public/llms-full.txt +1 -1
- package/docs/public/llms.txt +1 -1
- package/docs/tui-safety.md +1 -1
- package/docs/v2-roadmap.md +12 -7
- package/package.json +1 -1
- package/scripts/install.ps1 +1 -1
- package/scripts/install.sh +1 -1
- package/scripts/verify.js +88 -0
package/README.md
CHANGED
|
@@ -155,7 +155,7 @@ npx multimodel-dev-os@latest handoff build
|
|
|
155
155
|
| **v2.5.0** | Repository Intelligence Command Center | ✅ Released |
|
|
156
156
|
| **v2.6.0** | Real-Repo Onboarding & Adapter Sync | ✅ Released |
|
|
157
157
|
| **v2.7.0** | Website, Demo & Distribution System | ✅ Released |
|
|
158
|
-
| **v2.8.0** | Interactive TUI Dashboard & Plugin Hooks | ✅ Released |
|
|
158
|
+
| **v2.8.0 / v2.8.1** | Interactive TUI Dashboard & Plugin Hooks | ✅ Released |
|
|
159
159
|
| **v3.0.0** | Unified Autonomous Co-Pilot Ecosystem | 🔮 Future |
|
|
160
160
|
|
|
161
161
|
**[Full Roadmap →](https://rizvee.github.io/multimodel-dev-os/v2-roadmap)**
|
package/bin/multimodel-dev-os.js
CHANGED
|
@@ -52,7 +52,8 @@ function parseArgs(args) {
|
|
|
52
52
|
title: null,
|
|
53
53
|
approved: false,
|
|
54
54
|
intelligence: false,
|
|
55
|
-
onboarding: false
|
|
55
|
+
onboarding: false,
|
|
56
|
+
listActions: false
|
|
56
57
|
};
|
|
57
58
|
|
|
58
59
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -67,6 +68,8 @@ function parseArgs(args) {
|
|
|
67
68
|
params.caveman = true;
|
|
68
69
|
} else if (arg === '--dry-run' || arg === '-d') {
|
|
69
70
|
params.dryRun = true;
|
|
71
|
+
} else if (arg === '--list-actions') {
|
|
72
|
+
params.listActions = true;
|
|
70
73
|
} else if (arg === '--force' || arg === '-f') {
|
|
71
74
|
params.force = true;
|
|
72
75
|
} else if (arg === '--help' || arg === '-h') {
|
|
@@ -4407,28 +4410,33 @@ function handleDashboard(options) {
|
|
|
4407
4410
|
]
|
|
4408
4411
|
};
|
|
4409
4412
|
|
|
4410
|
-
if (!process.stdout.isTTY || !process.stdin.isTTY || options.dryRun) {
|
|
4411
|
-
console.log(`\n
|
|
4413
|
+
if (!process.stdout.isTTY || !process.stdin.isTTY || options.dryRun || options.listActions) {
|
|
4414
|
+
console.log(`\n📊 \x1b[36mMultiModel Dev OS Command Center (Headless/CI Preview)\x1b[0m`);
|
|
4415
|
+
console.log(`Target Workspace: \x1b[32m${options.target}\x1b[0m`);
|
|
4412
4416
|
console.log('==================================================');
|
|
4417
|
+
|
|
4418
|
+
const targetFlag = options.target === process.cwd() ? '' : ` --target "${options.target}"`;
|
|
4419
|
+
|
|
4413
4420
|
mainMenu.forEach(item => {
|
|
4414
4421
|
if (item.action === 'command') {
|
|
4415
|
-
console.log(`
|
|
4422
|
+
console.log(` \x1b[33m•\x1b[0m ${item.name.padEnd(30)} → \x1b[36mnpx multimodel-dev-os ${item.command}${targetFlag}\x1b[0m`);
|
|
4416
4423
|
} else if (item.action === 'submenu') {
|
|
4417
|
-
console.log(
|
|
4424
|
+
console.log(`\n \x1b[35m[${item.name.replace('...', '')}]\x1b[0m`);
|
|
4418
4425
|
submenus[item.menu].forEach(sub => {
|
|
4419
4426
|
if (sub.action === 'command') {
|
|
4420
|
-
console.log(` └─ ${sub.name}
|
|
4427
|
+
console.log(` └─ ${sub.name.padEnd(35)} → \x1b[36mnpx multimodel-dev-os ${sub.command}${targetFlag}\x1b[0m`);
|
|
4421
4428
|
}
|
|
4422
4429
|
});
|
|
4423
4430
|
}
|
|
4424
4431
|
});
|
|
4425
|
-
console.log('');
|
|
4432
|
+
console.log('\n\x1b[90m(Run with -t or --target to specify another workspace directory)\x1b[0m\n');
|
|
4426
4433
|
return;
|
|
4427
4434
|
}
|
|
4428
4435
|
|
|
4429
4436
|
const runCommandWrapper = (cmdStr) => {
|
|
4430
4437
|
console.clear();
|
|
4431
|
-
|
|
4438
|
+
const targetFlag = options.target === process.cwd() ? '' : ` --target "${options.target}"`;
|
|
4439
|
+
console.log(`\n\x1b[36mRunning Command:\x1b[0m npx multimodel-dev-os ${cmdStr}${targetFlag}`);
|
|
4432
4440
|
console.log('--------------------------------------------------\n');
|
|
4433
4441
|
try {
|
|
4434
4442
|
const cliPath = join(sourceRoot, 'bin', 'multimodel-dev-os.js');
|
|
@@ -4473,6 +4481,9 @@ function getPluginsDir(targetDir) {
|
|
|
4473
4481
|
|
|
4474
4482
|
function handlePluginList(options) {
|
|
4475
4483
|
const pluginsDir = getPluginsDir(options.target);
|
|
4484
|
+
const rawRelPath = relative(process.cwd(), join(sourceRoot, '.ai', 'plugins', 'plugin.example.yaml')).replace(/\\/g, '/');
|
|
4485
|
+
const examplePath = rawRelPath.startsWith('.') ? rawRelPath : `./${rawRelPath}`;
|
|
4486
|
+
|
|
4476
4487
|
if (!existsSync(pluginsDir)) {
|
|
4477
4488
|
if (options.json) {
|
|
4478
4489
|
console.log('[]');
|
|
@@ -4481,7 +4492,7 @@ function handlePluginList(options) {
|
|
|
4481
4492
|
console.log(`\n🔌 \x1b[36mInstalled Plugins in: ${options.target}\x1b[0m`);
|
|
4482
4493
|
console.log('==================================================');
|
|
4483
4494
|
console.log(' No plugins installed. Try:');
|
|
4484
|
-
console.log(
|
|
4495
|
+
console.log(` npx multimodel-dev-os plugin install ${examplePath} --approved`);
|
|
4485
4496
|
console.log('');
|
|
4486
4497
|
return;
|
|
4487
4498
|
}
|
|
@@ -4509,7 +4520,8 @@ function handlePluginList(options) {
|
|
|
4509
4520
|
console.log(`\n🔌 \x1b[36mInstalled Plugins in: ${options.target} (${plugins.length})\x1b[0m`);
|
|
4510
4521
|
console.log('==================================================');
|
|
4511
4522
|
if (plugins.length === 0) {
|
|
4512
|
-
console.log(' No plugins installed.');
|
|
4523
|
+
console.log(' No plugins installed. Try:');
|
|
4524
|
+
console.log(` npx multimodel-dev-os plugin install ${examplePath} --approved`);
|
|
4513
4525
|
} else {
|
|
4514
4526
|
plugins.forEach(p => {
|
|
4515
4527
|
console.log(`\n\x1b[32m* ${p.name} (v${p.version || '1.0.0'})\x1b[0m [slug: \x1b[33m${p.slug}\x1b[0m]`);
|
|
@@ -4521,6 +4533,11 @@ function handlePluginList(options) {
|
|
|
4521
4533
|
}
|
|
4522
4534
|
|
|
4523
4535
|
function handlePluginShow(slug, options) {
|
|
4536
|
+
if (!/^[a-z0-9-_]+$/i.test(slug)) {
|
|
4537
|
+
console.error(`\x1b[31mError: Invalid plugin slug '${slug}'. Slugs must be alphanumeric with dashes or underscores only.\x1b[0m`);
|
|
4538
|
+
process.exit(1);
|
|
4539
|
+
}
|
|
4540
|
+
|
|
4524
4541
|
const pluginsDir = getPluginsDir(options.target);
|
|
4525
4542
|
let p = null;
|
|
4526
4543
|
if (existsSync(pluginsDir)) {
|
|
@@ -4538,6 +4555,7 @@ function handlePluginShow(slug, options) {
|
|
|
4538
4555
|
|
|
4539
4556
|
if (!p) {
|
|
4540
4557
|
console.error(`\x1b[31mError: Plugin with slug '${slug}' is not installed.\x1b[0m`);
|
|
4558
|
+
console.error(` Run \x1b[36mplugin list\x1b[0m to see installed plugins, or validate a new plugin config using \x1b[36mplugin validate <path>\x1b[0m.`);
|
|
4541
4559
|
process.exit(1);
|
|
4542
4560
|
}
|
|
4543
4561
|
|
|
@@ -4586,13 +4604,14 @@ function handlePluginValidate(pluginPath, options) {
|
|
|
4586
4604
|
}
|
|
4587
4605
|
|
|
4588
4606
|
console.log(`\n📋 \x1b[34mValidating Plugin: ${pluginPath}\x1b[0m`);
|
|
4607
|
+
console.log('==================================================');
|
|
4589
4608
|
|
|
4590
4609
|
let errors = 0;
|
|
4591
4610
|
let plugin = null;
|
|
4592
4611
|
try {
|
|
4593
4612
|
plugin = parseYaml(readFileSync(fullPath, 'utf8'));
|
|
4594
4613
|
} catch (e) {
|
|
4595
|
-
console.error(` \x1b[31m✗ Failed to parse YAML: ${e.message}\x1b[0m`);
|
|
4614
|
+
console.error(` \x1b[31m✗ [SYNTAX] Failed to parse YAML: ${e.message}\x1b[0m`);
|
|
4596
4615
|
errors++;
|
|
4597
4616
|
}
|
|
4598
4617
|
|
|
@@ -4600,79 +4619,114 @@ function handlePluginValidate(pluginPath, options) {
|
|
|
4600
4619
|
const reqKeys = ['name', 'slug', 'version', 'description', 'author'];
|
|
4601
4620
|
reqKeys.forEach(k => {
|
|
4602
4621
|
if (plugin[k] === undefined || plugin[k] === null) {
|
|
4603
|
-
console.error(` \x1b[31m✗ Missing required key: ${k}\x1b[0m`);
|
|
4622
|
+
console.error(` \x1b[31m✗ [METADATA] Missing required key: ${k}\x1b[0m`);
|
|
4604
4623
|
errors++;
|
|
4605
4624
|
} else if (typeof plugin[k] !== 'string') {
|
|
4606
|
-
console.error(` \x1b[31m✗ Key '${k}' must be a string (found: ${typeof plugin[k]})\x1b[0m`);
|
|
4625
|
+
console.error(` \x1b[31m✗ [METADATA] Key '${k}' must be a string (found: ${typeof plugin[k]})\x1b[0m`);
|
|
4607
4626
|
errors++;
|
|
4627
|
+
} else if (k === 'slug') {
|
|
4628
|
+
if (!/^[a-z0-9-_]+$/i.test(plugin[k])) {
|
|
4629
|
+
console.error(` \x1b[31m✗ [METADATA] Key 'slug' must be alphanumeric with dashes or underscores only (found: "${plugin[k]}")\x1b[0m`);
|
|
4630
|
+
errors++;
|
|
4631
|
+
} else {
|
|
4632
|
+
console.log(` \x1b[32m✓ [METADATA] Key: slug ("${plugin[k]}")`);
|
|
4633
|
+
}
|
|
4608
4634
|
} else {
|
|
4609
|
-
console.log(` \x1b[32m
|
|
4635
|
+
console.log(` \x1b[32m✓ [METADATA] Key: ${k} ("${plugin[k]}")`);
|
|
4610
4636
|
}
|
|
4611
4637
|
});
|
|
4612
4638
|
|
|
4613
4639
|
if (plugin.allowed_file_patterns !== undefined) {
|
|
4614
4640
|
if (!Array.isArray(plugin.allowed_file_patterns)) {
|
|
4615
|
-
console.error(` \x1b[31m✗ allowed_file_patterns must be an array\x1b[0m`);
|
|
4641
|
+
console.error(` \x1b[31m✗ [SAFETY] allowed_file_patterns must be an array\x1b[0m`);
|
|
4616
4642
|
errors++;
|
|
4617
4643
|
} else {
|
|
4618
4644
|
plugin.allowed_file_patterns.forEach(pat => {
|
|
4619
4645
|
if (typeof pat !== 'string') {
|
|
4620
|
-
console.error(` \x1b[31m✗ allowed_file_patterns item must be a string: ${pat}\x1b[0m`);
|
|
4646
|
+
console.error(` \x1b[31m✗ [SAFETY] allowed_file_patterns item must be a string: ${pat}\x1b[0m`);
|
|
4647
|
+
errors++;
|
|
4648
|
+
return;
|
|
4649
|
+
}
|
|
4650
|
+
const normPattern = pat.replace(/\\/g, '/').trim();
|
|
4651
|
+
const isSafeSubdir = [
|
|
4652
|
+
'.ai/plugins/',
|
|
4653
|
+
'.ai/registries/',
|
|
4654
|
+
'.ai/templates/',
|
|
4655
|
+
'.ai/skills/',
|
|
4656
|
+
'.ai/checks/',
|
|
4657
|
+
'.ai/prompts/',
|
|
4658
|
+
'.ai/adapters/'
|
|
4659
|
+
].some(prefix => normPattern.startsWith(prefix));
|
|
4660
|
+
|
|
4661
|
+
const hasTraversal = normPattern.includes('..') || normPattern.startsWith('/');
|
|
4662
|
+
const isBlacklisted = [
|
|
4663
|
+
'.env',
|
|
4664
|
+
'.npmrc',
|
|
4665
|
+
'.git/',
|
|
4666
|
+
'node_modules/',
|
|
4667
|
+
'package.json',
|
|
4668
|
+
'package-lock.json'
|
|
4669
|
+
].some(black => normPattern.includes(black));
|
|
4670
|
+
|
|
4671
|
+
if (!isSafeSubdir || hasTraversal || isBlacklisted) {
|
|
4672
|
+
console.error(` \x1b[31m✗ [SAFETY] File pattern '${pat}' violates safety boundaries (must reside under .ai/ or adapters/, contain no '..', and exclude blacklisted files)\x1b[0m`);
|
|
4621
4673
|
errors++;
|
|
4622
4674
|
}
|
|
4623
4675
|
});
|
|
4624
|
-
|
|
4676
|
+
if (errors === 0) {
|
|
4677
|
+
console.log(` \x1b[32m✓ [SAFETY] allowed_file_patterns verified: ${plugin.allowed_file_patterns.length} items`);
|
|
4678
|
+
}
|
|
4625
4679
|
}
|
|
4626
4680
|
}
|
|
4627
4681
|
|
|
4628
4682
|
if (plugin.denied_file_patterns !== undefined) {
|
|
4629
4683
|
if (!Array.isArray(plugin.denied_file_patterns)) {
|
|
4630
|
-
console.error(` \x1b[31m✗ denied_file_patterns must be an array\x1b[0m`);
|
|
4684
|
+
console.error(` \x1b[31m✗ [SAFETY] denied_file_patterns must be an array\x1b[0m`);
|
|
4631
4685
|
errors++;
|
|
4632
4686
|
} else {
|
|
4633
4687
|
plugin.denied_file_patterns.forEach(pat => {
|
|
4634
4688
|
if (typeof pat !== 'string') {
|
|
4635
|
-
console.error(` \x1b[31m✗ denied_file_patterns item must be a string: ${pat}\x1b[0m`);
|
|
4689
|
+
console.error(` \x1b[31m✗ [SAFETY] denied_file_patterns item must be a string: ${pat}\x1b[0m`);
|
|
4636
4690
|
errors++;
|
|
4637
4691
|
}
|
|
4638
4692
|
});
|
|
4639
|
-
console.log(` \x1b[32m
|
|
4693
|
+
console.log(` \x1b[32m✓ [SAFETY] denied_file_patterns verified: ${plugin.denied_file_patterns.length} items`);
|
|
4640
4694
|
}
|
|
4641
4695
|
}
|
|
4642
4696
|
|
|
4643
4697
|
if (plugin.workflows !== undefined) {
|
|
4644
4698
|
if (typeof plugin.workflows !== 'object' || Array.isArray(plugin.workflows)) {
|
|
4645
|
-
console.error(` \x1b[31m✗ workflows must be an object\x1b[0m`);
|
|
4699
|
+
console.error(` \x1b[31m✗ [CAPABILITIES] workflows must be an object\x1b[0m`);
|
|
4646
4700
|
errors++;
|
|
4647
4701
|
} else {
|
|
4648
|
-
console.log(` \x1b[32m
|
|
4702
|
+
console.log(` \x1b[32m✓ [CAPABILITIES] workflows verified`);
|
|
4649
4703
|
}
|
|
4650
4704
|
}
|
|
4651
4705
|
|
|
4652
4706
|
if (plugin.templates !== undefined) {
|
|
4653
4707
|
if (typeof plugin.templates !== 'object' || Array.isArray(plugin.templates)) {
|
|
4654
|
-
console.error(` \x1b[31m✗ templates must be an object\x1b[0m`);
|
|
4708
|
+
console.error(` \x1b[31m✗ [CAPABILITIES] templates must be an object\x1b[0m`);
|
|
4655
4709
|
errors++;
|
|
4656
4710
|
} else {
|
|
4657
|
-
console.log(` \x1b[32m
|
|
4711
|
+
console.log(` \x1b[32m✓ [CAPABILITIES] templates verified`);
|
|
4658
4712
|
}
|
|
4659
4713
|
}
|
|
4660
4714
|
|
|
4661
4715
|
if (plugin.adapters !== undefined) {
|
|
4662
4716
|
if (typeof plugin.adapters !== 'object' || Array.isArray(plugin.adapters)) {
|
|
4663
|
-
console.error(` \x1b[31m✗ adapters must be an object\x1b[0m`);
|
|
4717
|
+
console.error(` \x1b[31m✗ [CAPABILITIES] adapters must be an object\x1b[0m`);
|
|
4664
4718
|
errors++;
|
|
4665
4719
|
} else {
|
|
4666
|
-
console.log(` \x1b[32m
|
|
4720
|
+
console.log(` \x1b[32m✓ [CAPABILITIES] adapters verified`);
|
|
4667
4721
|
}
|
|
4668
4722
|
}
|
|
4669
4723
|
|
|
4670
4724
|
if (plugin.safety_notes !== undefined) {
|
|
4671
4725
|
if (typeof plugin.safety_notes !== 'string') {
|
|
4672
|
-
console.error(` \x1b[31m✗ safety_notes must be a string\x1b[0m`);
|
|
4726
|
+
console.error(` \x1b[31m✗ [SAFETY] safety_notes must be a string\x1b[0m`);
|
|
4673
4727
|
errors++;
|
|
4674
4728
|
} else {
|
|
4675
|
-
console.log(` \x1b[32m
|
|
4729
|
+
console.log(` \x1b[32m✓ [SAFETY] safety_notes verified`);
|
|
4676
4730
|
}
|
|
4677
4731
|
}
|
|
4678
4732
|
}
|
|
@@ -4682,7 +4736,9 @@ function handlePluginValidate(pluginPath, options) {
|
|
|
4682
4736
|
if (options && options.noExit) return false;
|
|
4683
4737
|
process.exit(1);
|
|
4684
4738
|
} else {
|
|
4685
|
-
console.log(`\n\x1b[32m✔ Plugin '${plugin.slug || plugin.name}' is fully valid and compliant!\x1b[0m
|
|
4739
|
+
console.log(`\n\x1b[32m✔ Plugin '${plugin.slug || plugin.name}' is fully valid and compliant!\x1b[0m`);
|
|
4740
|
+
console.log(`\n\x1b[35mRecommended Next Command:\x1b[0m`);
|
|
4741
|
+
console.log(` npx multimodel-dev-os plugin install ${pluginPath} --approved\n`);
|
|
4686
4742
|
if (options && options.noExit) return true;
|
|
4687
4743
|
return true;
|
|
4688
4744
|
}
|
|
@@ -4772,14 +4828,15 @@ function handlePluginInstall(pluginPath, options) {
|
|
|
4772
4828
|
}
|
|
4773
4829
|
|
|
4774
4830
|
if (!options.approved) {
|
|
4831
|
+
console.error(`\x1b[31mError: Plugin cannot be installed without explicit user approval. Pass the --approved flag.\x1b[0m`);
|
|
4775
4832
|
console.log(`\n\x1b[33mPlanned Installation Actions:\x1b[0m`);
|
|
4776
4833
|
filesToCopy.forEach(item => {
|
|
4777
4834
|
const exists = existsSync(join(options.target, item.dest));
|
|
4778
4835
|
const suffix = exists ? ' \x1b[33m(will overwrite)\x1b[0m' : '';
|
|
4779
4836
|
console.log(` - \x1b[36m[WOULD COPY]\x1b[0m ${item.src} -> ${item.dest}${suffix}`);
|
|
4780
4837
|
});
|
|
4781
|
-
console.
|
|
4782
|
-
|
|
4838
|
+
console.error(`\n\x1b[31mError: Installation refused. Run with --approved to apply these changes.\x1b[0m\n`);
|
|
4839
|
+
process.exit(1);
|
|
4783
4840
|
}
|
|
4784
4841
|
|
|
4785
4842
|
filesToCopy.forEach(item => {
|
|
@@ -4799,7 +4856,22 @@ function handlePluginInstall(pluginPath, options) {
|
|
|
4799
4856
|
console.log(` \x1b[32mCOPY:\x1b[0m ${item.dest}`);
|
|
4800
4857
|
});
|
|
4801
4858
|
|
|
4802
|
-
console.log(`\n\x1b[32m✔ Plugin '${plugin.name}' installed successfully!\x1b[0m
|
|
4859
|
+
console.log(`\n\x1b[32m✔ Plugin '${plugin.name}' installed successfully!\x1b[0m`);
|
|
4860
|
+
console.log(`\nSummary of actions:`);
|
|
4861
|
+
console.log(` - Manifest registered: .ai/plugins/${slug}.yaml`);
|
|
4862
|
+
const assetCount = filesToCopy.length - 1;
|
|
4863
|
+
console.log(` - Synced assets: ${assetCount} file(s)`);
|
|
4864
|
+
|
|
4865
|
+
console.log(`\n\x1b[35mRecommended Next Commands:\x1b[0m`);
|
|
4866
|
+
console.log(` • View plugin details: npx multimodel-dev-os plugin show ${slug}`);
|
|
4867
|
+
console.log(` • Audit plugin health: npx multimodel-dev-os plugin status --target .`);
|
|
4868
|
+
if (plugin.workflows) {
|
|
4869
|
+
const wfKeys = Object.keys(plugin.workflows);
|
|
4870
|
+
if (wfKeys.length > 0) {
|
|
4871
|
+
console.log(` • Run custom workflow: npx multimodel-dev-os workflow run ${wfKeys[0]}`);
|
|
4872
|
+
}
|
|
4873
|
+
}
|
|
4874
|
+
console.log('');
|
|
4803
4875
|
}
|
|
4804
4876
|
|
|
4805
4877
|
function handlePluginStatus(options) {
|
|
@@ -4849,6 +4921,15 @@ function handlePluginStatus(options) {
|
|
|
4849
4921
|
console.log(` Status: \x1b[32mHealthy\x1b[0m (All ${presentCount}/${total} assets present)`);
|
|
4850
4922
|
} else {
|
|
4851
4923
|
console.log(` Status: \x1b[33mIncomplete\x1b[0m (${presentCount}/${total} assets present, ${missingCount} missing)`);
|
|
4924
|
+
console.log(` Missing Assets:`);
|
|
4925
|
+
p.allowed_file_patterns.forEach(pat => {
|
|
4926
|
+
const destPath = join(options.target, pat);
|
|
4927
|
+
if (!existsSync(destPath) || !statSync(destPath).isFile()) {
|
|
4928
|
+
console.log(` \x1b[31m✗\x1b[0m ${pat}`);
|
|
4929
|
+
}
|
|
4930
|
+
});
|
|
4931
|
+
console.log(` To fix: Reinstall the plugin or validate the configuration:`);
|
|
4932
|
+
console.log(` npx multimodel-dev-os plugin validate <path-to-plugin-source.yaml>`);
|
|
4852
4933
|
}
|
|
4853
4934
|
}
|
|
4854
4935
|
} catch (e) {
|
|
@@ -32,7 +32,7 @@ export default {
|
|
|
32
32
|
'license': 'https://opensource.org/licenses/MIT',
|
|
33
33
|
'url': 'https://github.com/rizvee/multimodel-dev-os',
|
|
34
34
|
'downloadUrl': 'https://www.npmjs.com/package/multimodel-dev-os',
|
|
35
|
-
'softwareVersion': '2.8.
|
|
35
|
+
'softwareVersion': '2.8.1',
|
|
36
36
|
'description': 'Portable, vendor-neutral AI Developer OS for multi-agent coding workflows.'
|
|
37
37
|
})
|
|
38
38
|
]
|
package/docs/dashboard.md
CHANGED
|
@@ -81,18 +81,20 @@ In automated environments (CI/CD pipelines, GitHub Actions) or when piped, the t
|
|
|
81
81
|
|
|
82
82
|
To prevent execution from hanging indefinitely, the TUI automatically detects non-TTY environments:
|
|
83
83
|
* **Interactive Mode**: Triggered when both `process.stdout.isTTY` and `process.stdin.isTTY` are true.
|
|
84
|
-
* **Headless Fallback**: Triggered when run in non-interactive shells, or if `--dry-run` is passed. The dashboard immediately prints a structured list of all menu options along with their corresponding CLI execution strings and exits cleanly.
|
|
84
|
+
* **Headless Fallback**: Triggered when run in non-interactive shells, or if `--dry-run` or `--list-actions` is passed. The dashboard immediately prints a structured, grouped list of all menu options along with their corresponding CLI execution strings (including active target flags) and exits cleanly.
|
|
85
85
|
|
|
86
86
|
Example headless output:
|
|
87
87
|
|
|
88
88
|
```txt
|
|
89
|
-
|
|
89
|
+
📊 MultiModel Dev OS Command Center (Headless/CI Preview)
|
|
90
|
+
Target Workspace: /workspace
|
|
90
91
|
==================================================
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
└─ Onboard:
|
|
92
|
+
• Active Workspace Status → npx multimodel-dev-os status
|
|
93
|
+
• Codebase Scan Analysis → npx multimodel-dev-os scan
|
|
94
|
+
|
|
95
|
+
[Onboarding Operations]
|
|
96
|
+
└─ Onboard: Analyze Repository → npx multimodel-dev-os onboard analyze
|
|
97
|
+
└─ Onboard: Recommendation Summary → npx multimodel-dev-os onboard recommend
|
|
96
98
|
...
|
|
97
99
|
```
|
|
98
100
|
|
package/docs/faq.md
CHANGED
|
@@ -97,7 +97,7 @@ The improvement proposal system enforces 12 strict safety checks including path
|
|
|
97
97
|
It is a terminal-based operational menu (`npx multimodel-dev-os dashboard` or `ui`) that allows developers to run diagnostics, sync rules, build memory indexes, and check proposal status in a guided interface. It uses Node.js's native `readline` module for raw-keypress navigation, keeping execution zero-dependency and fast.
|
|
98
98
|
|
|
99
99
|
**Will the TUI Dashboard hang in CI/CD pipelines?**
|
|
100
|
-
No. The dashboard detects when stdin or stdout is non-interactive. In non-TTY environments,
|
|
100
|
+
No. The dashboard detects when stdin or stdout is non-interactive. In non-TTY environments, or when `--dry-run` or `--list-actions` is passed, it prints a grouped preview of all options with their equivalent CLI commands and exits immediately, preventing pipelines from stalling.
|
|
101
101
|
|
|
102
102
|
**How secure is the Declarative Plugin system?**
|
|
103
103
|
Extremely secure. Plugins are strictly configuration-based (YAML manifest files). They cannot run arbitrary bash commands, execute node scripts, download npm packages, or make network calls. File copies are restricted to whitelisted `.ai/` and `adapters/` directories, and blacklists protect files like `.env`, `.git/`, `package.json`, and source code folders. Overwriting existing files requires `--force` and automatically generates backups.
|
package/docs/plugin-authoring.md
CHANGED
|
@@ -11,7 +11,7 @@ A plugin is defined by a single YAML or JSON file. The manifest schema structure
|
|
|
11
11
|
### Required Fields
|
|
12
12
|
|
|
13
13
|
* **`name`** (string): The human-readable name of the plugin (e.g. `Git Integration Plugin`).
|
|
14
|
-
* **`slug`** (string): A unique, URL-friendly slug identifier (e.g. `git-integration`). This slug determines the filename under `.ai/plugins/<slug>.yaml` after installation.
|
|
14
|
+
* **`slug`** (string): A unique, URL-friendly slug identifier. Must consist strictly of lowercase alphanumeric characters, dashes, or underscores (`/^[a-z0-9-_]+$/i`, e.g. `git-integration`). This slug determines the filename under `.ai/plugins/<slug>.yaml` after installation.
|
|
15
15
|
* **`version`** (string): Semantic version string (e.g. `1.0.0`).
|
|
16
16
|
* **`description`** (string): A brief summary of the capabilities introduced by the plugin.
|
|
17
17
|
* **`author`** (string): The creator's name or team.
|
package/docs/plugin-hooks.md
CHANGED
|
@@ -78,3 +78,12 @@ npx multimodel-dev-os@latest plugin status [--target <path>]
|
|
|
78
78
|
When a plugin installation causes file conflicts:
|
|
79
79
|
* By default, the installer aborts and prints a list of conflicting files.
|
|
80
80
|
* Running with `--force` overwrites the destination files but automatically copies the existing file to `<filename>.bak` in the same directory.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Validation & Safe Execution Gates
|
|
85
|
+
|
|
86
|
+
To prevent path traversal and enforce robust script auditing:
|
|
87
|
+
* **Alphanumeric Slug Constraints**: Slugs are validated against `/^[a-z0-9-_]+$/i` to block directory escapes when writing to `.ai/plugins/<slug>.yaml`.
|
|
88
|
+
* **Path Boundary checks**: The `plugin validate` CLI command automatically parses `allowed_file_patterns` to assert they fit within whitelisted `.ai/` and `adapters/` folders, checking that no `..` traversal or blacklisted files are referenced.
|
|
89
|
+
* **Non-zero CI exit codes**: If `plugin install` is called without the `--approved` flag, it prints planned actions and exits with **exit code 1** to abort scripting pipelines safely.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# MultiModel Dev OS — Comprehensive AI Assistant Discoverability Guide (v2.8.
|
|
1
|
+
# MultiModel Dev OS — Comprehensive AI Assistant Discoverability Guide (v2.8.1 Stable Release)
|
|
2
2
|
|
|
3
3
|
MultiModel Dev OS is a repository-level porting specification designed to align context and instructions across diverse developer tools and AI models.
|
|
4
4
|
|
package/docs/public/llms.txt
CHANGED
package/docs/tui-safety.md
CHANGED
|
@@ -36,7 +36,7 @@ The installer restricts all plugin file copies to specific directories inside th
|
|
|
36
36
|
* `.ai/prompts/`
|
|
37
37
|
* `.ai/adapters/`
|
|
38
38
|
|
|
39
|
-
If a plugin manifest attempts to write to paths outside of these folders (such as `src/`, `lib/`, or `tests/`), the validator immediately throws an error and aborts the installation.
|
|
39
|
+
If a plugin manifest attempts to write to paths outside of these folders (such as `src/`, `lib/`, or `tests/`), the validator immediately throws an error and aborts the installation. To guarantee safety, the validation phase restricts plugin `slug` parameters to alphanumeric characters with dashes and underscores only (`/^[a-z0-9-_]+$/i`), hard-blocking directory traversal patterns (`..`, `/`, `\`) to prevent manifest write escapes.
|
|
40
40
|
|
|
41
41
|
### 3. Blacklist Enforcement
|
|
42
42
|
The system hard-blocks write operations to critical files or directories to prevent credentials exposure or project hijack:
|
package/docs/v2-roadmap.md
CHANGED
|
@@ -7,7 +7,7 @@ This document outlines the development path, completed milestones, and future pl
|
|
|
7
7
|
## 1. Current Status
|
|
8
8
|
|
|
9
9
|
> [!IMPORTANT]
|
|
10
|
-
> **v2.
|
|
10
|
+
> **v2.8.1 is the active stable release** on the public npm registry. All features below marked ✅ are shipped and production-ready.
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
@@ -59,6 +59,13 @@ This document outlines the development path, completed milestones, and future pl
|
|
|
59
59
|
- Created docs-first examples for key developer workflows
|
|
60
60
|
- Updated sitemaps, model registries, and search indices
|
|
61
61
|
|
|
62
|
+
### v2.8.0 / v2.8.1 — Interactive TUI Dashboard & Plugin Hooks ✅
|
|
63
|
+
- **Interactive TUI Dashboard**: Added `dashboard`/`ui` command launching a zero-dependency keyboard-interactive command center built with Node's native `readline` module.
|
|
64
|
+
- **Declarative Plugin Hooks**: Added `plugin` command suite (`list`, `show`, `validate`, `install`, `status`) and JSON schema to securely extend workspace templates, workflows, and skills.
|
|
65
|
+
- **Secure Plugin Installer**: Supports `--approved` execution gate, path whitelisting to `.ai/` and `adapters/` directories, and automatic conflict `.bak` backups.
|
|
66
|
+
- **Headless Fallback & CI Polish**: Polish dry-run outputs and added `--list-actions` parameter to prevent TUI hangs in CI.
|
|
67
|
+
- **Path Traversal Hardening**: Enforce alphanumeric slug checks (`/^[a-z0-9-_]+$/i`) and pattern validation bounds to block traversal vectors.
|
|
68
|
+
|
|
62
69
|
---
|
|
63
70
|
|
|
64
71
|
## 3. Publishing Workflow
|
|
@@ -66,7 +73,7 @@ This document outlines the development path, completed milestones, and future pl
|
|
|
66
73
|
All releases follow this strict publishing checklist:
|
|
67
74
|
|
|
68
75
|
1. Bump version in `package.json`
|
|
69
|
-
2. Run `npm run verify` (
|
|
76
|
+
2. Run `npm run verify` (220+ assertions must pass)
|
|
70
77
|
3. Run `npm run docs:build` to verify documentation
|
|
71
78
|
4. Run `npm publish --dry-run` to review package hygiene
|
|
72
79
|
5. Set `MMDO_ALLOW_PUBLISH=true` and publish:
|
|
@@ -76,12 +83,10 @@ All releases follow this strict publishing checklist:
|
|
|
76
83
|
|
|
77
84
|
---
|
|
78
85
|
|
|
79
|
-
## 4. Upcoming: v2.
|
|
86
|
+
## 4. Upcoming: v2.9.0 — Auto-Detection & Custom Adaptors
|
|
80
87
|
|
|
81
|
-
* **
|
|
82
|
-
* **
|
|
83
|
-
* **Custom Workflow Authoring**: User-defined workflow definitions beyond bundled registries
|
|
84
|
-
* **Adapter Auto-Detection**: Detect installed tools and automatically recommend adapter setup
|
|
88
|
+
* **Adapter Auto-Detection**: Detect installed tools and automatically recommend adapter setup.
|
|
89
|
+
* **Custom Adapter Hookups**: Programmatic hooks allowing plugins to register physical adapter configurations dynamically.
|
|
85
90
|
|
|
86
91
|
---
|
|
87
92
|
|
package/package.json
CHANGED
package/scripts/install.ps1
CHANGED
package/scripts/install.sh
CHANGED
package/scripts/verify.js
CHANGED
|
@@ -456,11 +456,99 @@ try {
|
|
|
456
456
|
console.error(` ${RED}✗${NC} CLI help is missing scan, memory, status, workflow, or handoff commands`);
|
|
457
457
|
fail++;
|
|
458
458
|
}
|
|
459
|
+
|
|
460
|
+
if (helpOutput.includes('dashboard') && helpOutput.includes('ui') && helpOutput.includes('plugin')) {
|
|
461
|
+
console.log(` ${GREEN}✓${NC} CLI help includes dashboard, ui, and plugin commands`);
|
|
462
|
+
pass++;
|
|
463
|
+
} else {
|
|
464
|
+
console.error(` ${RED}✗${NC} CLI help is missing dashboard, ui, or plugin commands`);
|
|
465
|
+
fail++;
|
|
466
|
+
}
|
|
459
467
|
} catch (e) {
|
|
460
468
|
console.error(` ${RED}✗${NC} node bin/multimodel-dev-os.js --help failed: ${e.message}`);
|
|
461
469
|
fail++;
|
|
462
470
|
}
|
|
463
471
|
|
|
472
|
+
// --- v2.8.0 / v2.8.1 Dashboard & Plugin Tests ---
|
|
473
|
+
console.log('\nRunning TUI Dashboard & Plugin Pre-Flight Tests...');
|
|
474
|
+
|
|
475
|
+
// 1. Dashboard dry-run check
|
|
476
|
+
try {
|
|
477
|
+
const output = execSync('node bin/multimodel-dev-os.js dashboard --dry-run', { cwd: projectRoot, encoding: 'utf8' });
|
|
478
|
+
if (output.includes('Headless/CI Preview') && output.includes('npx multimodel-dev-os')) {
|
|
479
|
+
console.log(` ${GREEN}✓${NC} dashboard --dry-run executes successfully and displays headless preview`);
|
|
480
|
+
pass++;
|
|
481
|
+
} else {
|
|
482
|
+
console.error(` ${RED}✗${NC} dashboard --dry-run output is missing preview strings`);
|
|
483
|
+
fail++;
|
|
484
|
+
}
|
|
485
|
+
} catch (e) {
|
|
486
|
+
console.error(` ${RED}✗${NC} dashboard --dry-run execution failed: ${e.message}`);
|
|
487
|
+
fail++;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// 2. Dashboard list-actions check
|
|
491
|
+
try {
|
|
492
|
+
const output = execSync('node bin/multimodel-dev-os.js dashboard --list-actions', { cwd: projectRoot, encoding: 'utf8' });
|
|
493
|
+
if (output.includes('Headless/CI Preview') && output.includes('npx multimodel-dev-os')) {
|
|
494
|
+
console.log(` ${GREEN}✓${NC} dashboard --list-actions executes successfully and displays headless preview`);
|
|
495
|
+
pass++;
|
|
496
|
+
} else {
|
|
497
|
+
console.error(` ${RED}✗${NC} dashboard --list-actions output is missing preview strings`);
|
|
498
|
+
fail++;
|
|
499
|
+
}
|
|
500
|
+
} catch (e) {
|
|
501
|
+
console.error(` ${RED}✗${NC} dashboard --list-actions execution failed: ${e.message}`);
|
|
502
|
+
fail++;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// 3. Plugin validation check
|
|
506
|
+
try {
|
|
507
|
+
const output = execSync('node bin/multimodel-dev-os.js plugin validate .ai/plugins/plugin.example.yaml', { cwd: projectRoot, encoding: 'utf8' });
|
|
508
|
+
if (output.includes('fully valid and compliant')) {
|
|
509
|
+
console.log(` ${GREEN}✓${NC} plugin validate on example manifest passes successfully`);
|
|
510
|
+
pass++;
|
|
511
|
+
} else {
|
|
512
|
+
console.error(` ${RED}✗${NC} plugin validate on example manifest failed to report compliance`);
|
|
513
|
+
fail++;
|
|
514
|
+
}
|
|
515
|
+
} catch (e) {
|
|
516
|
+
console.error(` ${RED}✗${NC} plugin validate execution failed: ${e.message}`);
|
|
517
|
+
fail++;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// 4. Plugin install refusal check (no --approved, should exit with code 1)
|
|
521
|
+
try {
|
|
522
|
+
execSync('node bin/multimodel-dev-os.js plugin install .ai/plugins/plugin.example.yaml', { cwd: projectRoot, stdio: 'pipe' });
|
|
523
|
+
console.error(` ${RED}✗${NC} plugin install without --approved should have exited with code 1, but exited with 0`);
|
|
524
|
+
fail++;
|
|
525
|
+
} catch (e) {
|
|
526
|
+
if (e.status === 1) {
|
|
527
|
+
const stdErrOut = e.stderr ? e.stderr.toString() : '';
|
|
528
|
+
const stdOutOut = e.stdout ? e.stdout.toString() : '';
|
|
529
|
+
if (stdErrOut.includes('Installation refused') || stdOutOut.includes('Installation refused')) {
|
|
530
|
+
console.log(` ${GREEN}✓${NC} plugin install without --approved correctly refuses and exits with code 1`);
|
|
531
|
+
pass++;
|
|
532
|
+
} else {
|
|
533
|
+
console.error(` ${RED}✗${NC} plugin install without --approved exited with 1 but missing refusal message`);
|
|
534
|
+
fail++;
|
|
535
|
+
}
|
|
536
|
+
} else {
|
|
537
|
+
console.error(` ${RED}✗${NC} plugin install without --approved failed with unexpected code ${e.status}: ${e.message}`);
|
|
538
|
+
fail++;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// 5. Plugin status check
|
|
543
|
+
try {
|
|
544
|
+
execSync('node bin/multimodel-dev-os.js plugin status', { cwd: projectRoot, stdio: 'ignore' });
|
|
545
|
+
console.log(` ${GREEN}✓${NC} plugin status executes without crashing`);
|
|
546
|
+
pass++;
|
|
547
|
+
} catch (e) {
|
|
548
|
+
console.error(` ${RED}✗${NC} plugin status execution failed: ${e.message}`);
|
|
549
|
+
fail++;
|
|
550
|
+
}
|
|
551
|
+
|
|
464
552
|
// Verify docs mention memory build
|
|
465
553
|
try {
|
|
466
554
|
const mdContent = readFileSync(join(projectRoot, 'docs', 'hash-compressed-memory.md'), 'utf8');
|