multimodel-dev-os 3.1.0 → 3.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.
Files changed (53) hide show
  1. package/README.md +1 -0
  2. package/docs/.vitepress/config.js +1 -1
  3. package/docs/index.md +5 -5
  4. package/docs/npm-publishing.md +5 -5
  5. package/docs/package-safety.md +16 -0
  6. package/docs/public/llms-full.txt +1 -1
  7. package/docs/public/llms.txt +1 -1
  8. package/docs/public/sitemap.xml +5 -0
  9. package/docs/release-policy.md +6 -5
  10. package/docs/testing.md +12 -2
  11. package/docs/v3-roadmap.md +8 -2
  12. package/package.json +5 -2
  13. package/scripts/build-cli.js +45 -3
  14. package/scripts/check-build-fresh.js +52 -0
  15. package/scripts/install.ps1 +1 -1
  16. package/scripts/install.sh +1 -1
  17. package/scripts/verify.js +128 -12
  18. package/scripts/verify.sh +10 -0
  19. package/src/catalog/loader.js +117 -0
  20. package/src/cli/args.js +118 -0
  21. package/src/cli/help.js +60 -0
  22. package/src/cli/main.js +5718 -0
  23. package/src/core/globals.js +52 -0
  24. package/src/core/hashes.js +15 -0
  25. package/src/core/policy.js +36 -0
  26. package/src/core/security.js +61 -0
  27. package/src/core/yaml.js +136 -0
  28. package/src/plugin/manifest.js +95 -0
  29. package/src/registry/sources.js +40 -0
  30. package/src/registry/validation.js +45 -0
  31. package/tests/README.md +37 -0
  32. package/tests/fixtures/README.md +22 -0
  33. package/tests/fixtures/custom-template-example/README.md +10 -0
  34. package/tests/fixtures/proposals/approved-append-line.md +28 -0
  35. package/tests/fixtures/proposals/approved-create-file.md +29 -0
  36. package/tests/fixtures/proposals/approved-replace-text.md +30 -0
  37. package/tests/fixtures/proposals/existing-create-file-no-overwrite.md +29 -0
  38. package/tests/fixtures/proposals/no-operations.md +18 -0
  39. package/tests/fixtures/proposals/path-traversal.md +29 -0
  40. package/tests/fixtures/proposals/pending-proposal.md +29 -0
  41. package/tests/fixtures/proposals/protected-path.md +29 -0
  42. package/tests/fixtures/proposals/replace-multiple-without-allow.md +30 -0
  43. package/tests/fixtures/registry-overrides/README.md +20 -0
  44. package/tests/smoke/README.md +37 -0
  45. package/tests/smoke/cli-smoke.md +49 -0
  46. package/tests/unit/build-output.test.js +40 -0
  47. package/tests/unit/catalog-loader.test.js +44 -0
  48. package/tests/unit/path-safety.test.js +62 -0
  49. package/tests/unit/plugin-manifest.test.js +94 -0
  50. package/tests/unit/prepublish-guard.test.js +35 -0
  51. package/tests/unit/registry-policy.test.js +46 -0
  52. package/tests/unit/registry-url-validation.test.js +64 -0
  53. package/tests/unit/yaml.test.js +92 -0
@@ -0,0 +1,117 @@
1
+ import { existsSync, readFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { sourceRoot } from '../core/globals.js';
4
+ import { parseYaml } from '../core/yaml.js';
5
+ import { loadRegistrySources } from '../registry/sources.js';
6
+ import { loadRegistryPolicy } from '../core/policy.js';
7
+ import { validateRegistryUrl } from '../registry/validation.js';
8
+
9
+ export function loadCatalog(options = {}) {
10
+ let catalog;
11
+ if (options.allSources) {
12
+ catalog = loadAllCatalogs(options);
13
+ } else if (options.source) {
14
+ catalog = loadCatalogFromSource(options.source, options);
15
+ } else {
16
+ const path = join(sourceRoot, '.ai', 'plugins', 'catalog.yaml');
17
+ try {
18
+ if (existsSync(path)) {
19
+ const reg = parseYaml(readFileSync(path, 'utf8'));
20
+ catalog = reg.catalog || { plugins: [] };
21
+ } else {
22
+ catalog = { plugins: [] };
23
+ }
24
+ } catch (e) {
25
+ catalog = { plugins: [] };
26
+ }
27
+ (catalog.plugins || []).forEach(p => { p._source = 'bundled'; });
28
+ }
29
+ return catalog;
30
+ }
31
+
32
+ export function loadCatalogFromSource(source, options = {}) {
33
+ if (!source || source === 'bundled') {
34
+ return loadCatalog();
35
+ } else if (source === 'local') {
36
+ const localPath = join(options.target || process.cwd(), '.ai', 'plugins', 'catalog.yaml');
37
+ try {
38
+ if (existsSync(localPath)) {
39
+ const reg = parseYaml(readFileSync(localPath, 'utf8'));
40
+ const catalog = reg.catalog || { plugins: [] };
41
+ (catalog.plugins || []).forEach(p => { p._source = 'local'; });
42
+ return catalog;
43
+ }
44
+ } catch (e) {}
45
+ return { plugins: [] };
46
+ } else if (source.startsWith('remote:')) {
47
+ const regName = source.substring(7);
48
+ const sources = loadRegistrySources();
49
+ const src = sources.find(s => s.name === regName);
50
+ if (src && src.type !== 'local') {
51
+ const policy = loadRegistryPolicy(options.target || process.cwd());
52
+ try {
53
+ validateRegistryUrl(src.url, policy);
54
+ } catch (err) {
55
+ console.error(`\x1b[31mError: Registry '${regName}' has an invalid URL: ${err.message}\x1b[0m`);
56
+ process.exit(1);
57
+ }
58
+ }
59
+ const cachePath = join(sourceRoot, '.ai', 'registry-cache', regName, 'catalog.yaml');
60
+ try {
61
+ if (existsSync(cachePath)) {
62
+ const reg = parseYaml(readFileSync(cachePath, 'utf8'));
63
+ const catalog = reg.catalog || { plugins: [] };
64
+ (catalog.plugins || []).forEach(p => { p._source = `remote:${regName}`; });
65
+ return catalog;
66
+ }
67
+ } catch (e) {}
68
+ return { plugins: [] };
69
+ }
70
+ return { plugins: [] };
71
+ }
72
+
73
+ export function loadAllCatalogs(options = {}) {
74
+ const sources = loadRegistrySources();
75
+ const policy = loadRegistryPolicy(options.target || process.cwd());
76
+ const allPlugins = [];
77
+
78
+ // Always include bundled
79
+ const bundled = loadCatalog();
80
+ (bundled.plugins || []).forEach(p => { p._source = 'bundled'; allPlugins.push(p); });
81
+
82
+ // Include local workspace catalog if different from bundled
83
+ const localPath = join(options.target || process.cwd(), '.ai', 'plugins', 'catalog.yaml');
84
+ if (existsSync(localPath)) {
85
+ try {
86
+ const localCat = parseYaml(readFileSync(localPath, 'utf8'));
87
+ const localPlugins = (localCat.catalog || {}).plugins || [];
88
+ localPlugins.forEach(p => {
89
+ if (!allPlugins.some(bp => bp.slug === p.slug)) {
90
+ p._source = 'local';
91
+ allPlugins.push(p);
92
+ }
93
+ });
94
+ } catch (e) {}
95
+ }
96
+
97
+ // Include remote caches if policy allows
98
+ if (policy.allow_remote_registries) {
99
+ sources.filter(s => s.type !== 'local' && s.enabled).forEach(s => {
100
+ const cachePath = join(sourceRoot, '.ai', 'registry-cache', s.name, 'catalog.yaml');
101
+ if (existsSync(cachePath)) {
102
+ try {
103
+ const remoteCat = parseYaml(readFileSync(cachePath, 'utf8'));
104
+ const remotePlugins = (remoteCat.catalog || {}).plugins || [];
105
+ remotePlugins.forEach(p => {
106
+ if (!allPlugins.some(bp => bp.slug === p.slug)) {
107
+ p._source = `remote:${s.name}`;
108
+ allPlugins.push(p);
109
+ }
110
+ });
111
+ } catch (e) {}
112
+ }
113
+ });
114
+ }
115
+
116
+ return { plugins: allPlugins };
117
+ }
@@ -0,0 +1,118 @@
1
+ import { resolve } from 'path';
2
+
3
+ export function parseArgs(args) {
4
+ const params = {
5
+ command: null,
6
+ target: process.cwd(),
7
+ template: 'general-app',
8
+ adapters: [],
9
+ caveman: false,
10
+ dryRun: false,
11
+ force: false,
12
+ help: false,
13
+ tokens: false,
14
+ modelPreset: null,
15
+ agent: null,
16
+ stack: null,
17
+ mobile: null,
18
+ aiApp: null,
19
+ json: false,
20
+ threshold: null,
21
+ registry: null,
22
+ allRegistries: false,
23
+ release: false,
24
+ type: 'unknown',
25
+ tags: '',
26
+ files: '',
27
+ title: null,
28
+ approved: false,
29
+ intelligence: false,
30
+ onboarding: false,
31
+ listActions: false,
32
+ category: null,
33
+ source: null,
34
+ allSources: false
35
+ };
36
+
37
+ for (let i = 0; i < args.length; i++) {
38
+ const arg = args[i];
39
+ if (arg === '--target' || arg === '-t') {
40
+ params.target = resolve(args[++i]);
41
+ } else if (arg === '--template') {
42
+ params.template = args[++i];
43
+ } else if (arg === '--adapter' || arg === '-a') {
44
+ params.adapters.push(args[++i]);
45
+ } else if (arg === '--caveman') {
46
+ params.caveman = true;
47
+ } else if (arg === '--dry-run' || arg === '-d') {
48
+ params.dryRun = true;
49
+ } else if (arg === '--list-actions') {
50
+ params.listActions = true;
51
+ } else if (arg === '--force' || arg === '-f') {
52
+ params.force = true;
53
+ } else if (arg === '--help' || arg === '-h') {
54
+ params.help = true;
55
+ } else if (arg === '--tokens') {
56
+ params.tokens = true;
57
+ } else if (arg === '--all-registries') {
58
+ params.allRegistries = true;
59
+ } else if (arg === '--release') {
60
+ params.release = true;
61
+ } else if (arg === '--intelligence') {
62
+ params.intelligence = true;
63
+ } else if (arg === '--onboarding') {
64
+ params.onboarding = true;
65
+ } else if (arg === '--json') {
66
+ params.json = true;
67
+ } else if (arg === '--threshold') {
68
+ params.threshold = args[++i];
69
+ } else if (arg === '--registry') {
70
+ params.registry = args[++i];
71
+ } else if (arg === '--model-preset') {
72
+ params.modelPreset = args[++i];
73
+ } else if (arg === '--agent') {
74
+ params.agent = args[++i];
75
+ } else if (arg === '--stack') {
76
+ params.stack = args[++i];
77
+ } else if (arg === '--mobile') {
78
+ params.mobile = args[++i];
79
+ } else if (arg === '--type') {
80
+ params.type = args[++i];
81
+ } else if (arg === '--tags') {
82
+ params.tags = args[++i];
83
+ } else if (arg === '--files') {
84
+ params.files = args[++i];
85
+ } else if (arg === '--title') {
86
+ params.title = args[++i];
87
+ } else if (arg === '--approved') {
88
+ params.approved = true;
89
+ } else if (arg === '--category') {
90
+ params.category = args[++i];
91
+ } else if (arg === '--source') {
92
+ params.source = args[++i];
93
+ } else if (arg === '--all-sources') {
94
+ params.allSources = true;
95
+ } else if (!params.command && !arg.startsWith('-')) {
96
+ params.command = arg;
97
+ }
98
+ }
99
+ return params;
100
+ }
101
+
102
+ export function getPositionalArgs(args) {
103
+ const positionalArgs = [];
104
+ for (let i = 0; i < args.length; i++) {
105
+ const arg = args[i];
106
+ if (arg === '--target' || arg === '-t' || arg === '--template' || arg === '--adapter' || arg === '-a' ||
107
+ arg === '--threshold' || arg === '--registry' || arg === '--model-preset' || arg === '--agent' ||
108
+ arg === '--stack' || arg === '--mobile' || arg === '--type' || arg === '--tags' || arg === '--files' ||
109
+ arg === '--title' || arg === '--category') {
110
+ i++; // skip next arg (its value)
111
+ } else if (arg.startsWith('-')) {
112
+ // it's a flag, skip
113
+ } else {
114
+ positionalArgs.push(arg);
115
+ }
116
+ }
117
+ return positionalArgs;
118
+ }
@@ -0,0 +1,60 @@
1
+ import { version } from '../core/globals.js';
2
+
3
+ export function showHelp() {
4
+ console.log(`\n🧠 \x1b[36mmultimodel-dev-os CLI v${version}\x1b[0m`);
5
+ console.log('====================================');
6
+ console.log('Usage: node bin/multimodel-dev-os.js <command> [options]\n');
7
+ console.log('Commands:');
8
+ console.log(' init Initialize a project with configs and adapters');
9
+ console.log(' scan Scan project structure and framework signals');
10
+ console.log(' status Show compact dashboard summarizing repository intelligence state');
11
+ console.log(' dashboard Launch the interactive terminal command center (alias: ui)');
12
+ console.log(' memory <subcmd> Manage hash-compressed codebase memory (subcmd: build, refresh, diff)');
13
+ console.log(' feedback <subcmd> Manage developer feedback loops (subcmd: add, list, summarize)');
14
+ console.log(' improve <subcmd> Manage codebase self-improvement proposals (subcmd: propose, review, status, validate, diff, apply, log)');
15
+ console.log(' workflow <subcmd> Orchestrate read-only development workflow pipelines (subcmd: list, show, plan, run)');
16
+ console.log(' handoff <subcmd> Compile or print token-compressed agent session summaries (subcmd: build, show)');
17
+ console.log(' onboard <subcmd> Safely integrate MultiModel Dev OS into existing repo (subcmd: analyze, recommend, plan, apply, status)');
18
+ console.log(' adapter <subcmd> Manage and sync rule/settings files for IDE adapters (subcmd: status, diff, sync)');
19
+ console.log(' plugin <subcmd> Manage declarative plugins (subcmd: list, show, validate, install, status)');
20
+ console.log(' catalog <subcmd> Manage Workflow Marketplace & Plugin Catalog (subcmd: list, search, show, categories, recommend, install, status)');
21
+ console.log(' registry <subcmd> Manage trusted remote catalog registries (subcmd: list, add, remove, sync, status, verify, show, cache)');
22
+ console.log(' verify Validate structural integrity of an existing project');
23
+ console.log(' templates List all built-in template profiles with details');
24
+ console.log(' list-templates Alias for templates command');
25
+ console.log(' show-template <t> Inspect detailed stack specifications of template <t>');
26
+ console.log(' doctor Advisory checkup of project compatibility loops and ignored folders');
27
+ console.log(' validate Strict validation checks to verify directory schema compliance');
28
+ console.log(' validate-template Validate registry keys and source folder files for template');
29
+ console.log(' validate-adapter Validate registry keys and source assets for IDE adapter');
30
+ console.log(' validate-skill Verify custom skill conforms to core prompt structure');
31
+ console.log(' models List registered model aliases in the capabilities registry');
32
+ console.log(' show-model <m> View specifications of model <m> in registry');
33
+ console.log(' providers List configured AI provider API endpoints');
34
+ console.log(' route-model <tsk> Suggest optimal model mapping for task <tsk>');
35
+ console.log(' adapters List IDE and terminal tool adapters');
36
+ console.log(' show-adapter <a> Inspect config specifications of adapter <a>');
37
+ console.log(' skills List active skills custom prompts in target workspace');
38
+ console.log(' show-skill <s> View prompt contents of target workspace skill <s>\n');
39
+ console.log('Options:');
40
+ console.log(' -t, --target <path> Target folder destination (default: current working directory)');
41
+ console.log(' --type <type> Feedback classification (correction, preference, bug, etc.)');
42
+ console.log(' --tags <list> Comma-separated descriptor tags for feedback');
43
+ console.log(' --files <list> Comma-separated target files for feedback');
44
+ console.log(' --category <name> Filter catalog plugins list by category');
45
+ console.log(' --source <src> Catalog source filter: bundled, local, or remote:<name>');
46
+ console.log(' --all-sources Include all enabled catalog sources in listings');
47
+ console.log(' --title <text> Specifies title for codebase improvement proposal');
48
+ console.log(' --approved Explicitly approve and execute proposal/onboarding/adapter sync writes');
49
+ console.log(' --template <name> Template profile: nextjs-saas, expo-react-native-android, etc.');
50
+ console.log(' -a, --adapter <name> Inject specific adapter: cursor, claude, vscode, gemini, etc.');
51
+ console.log(' --caveman Use minimal-token templates (~79% fewer tokens)');
52
+ console.log(' --tokens Run a deeper token-sink size analysis during doctor checkup');
53
+ console.log(' --intelligence Run diagnostic checkup of repository intelligence config');
54
+ console.log(' --onboarding Run diagnostic checkup of repository onboarding setup');
55
+ console.log(' --json Output raw JSON data for listing commands (models, adapters, templates)');
56
+ console.log(' --threshold <val> Set custom size threshold for doctor tokens checks (e.g. 50KB)');
57
+ console.log(' --registry <path> Override default registry (for templates/adapters list or check)');
58
+ console.log(' -d, --dry-run Preview planned file actions without modifying the filesystem');
59
+ console.log(' -f, --force Overwrite existing files without prompting\n');
60
+ }