multimodel-dev-os 3.1.0 → 3.5.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/.ai/policies/registry-policy.yaml +29 -1
- package/.ai/registries/trusted-keys.yaml +12 -0
- package/.ai/schema/registry-manifest.schema.json +31 -2
- package/.ai/schema/registry-policy.schema.json +37 -1
- package/.ai/schema/trusted-keys.schema.json +69 -0
- package/AGENTS.md +22 -26
- package/MEMORY.md +34 -11
- package/README.md +2 -1
- package/RUNBOOK.md +28 -36
- package/TASKS.md +15 -5
- package/bin/multimodel-dev-os.js +1366 -548
- package/docs/.vitepress/config.js +3 -1
- package/docs/architecture.md +3 -1
- package/docs/index.md +5 -5
- package/docs/npm-publishing.md +5 -5
- package/docs/package-safety.md +17 -0
- package/docs/public/llms-full.txt +5 -1
- package/docs/public/llms.txt +6 -1
- package/docs/public/sitemap.xml +15 -0
- package/docs/registry-policy.md +29 -1
- package/docs/registry-security.md +73 -6
- package/docs/registry-signing.md +70 -0
- package/docs/registry-sync.md +5 -2
- package/docs/registry-trust-store.md +66 -0
- package/docs/release-policy.md +6 -5
- package/docs/security-threat-model.md +96 -0
- package/docs/testing.md +25 -2
- package/docs/trusted-registries.md +1 -1
- package/docs/v3-roadmap.md +17 -6
- package/docs/v3.5.0-readiness.md +46 -0
- package/package.json +5 -2
- package/scripts/build-cli.js +45 -3
- package/scripts/check-build-fresh.js +52 -0
- package/scripts/install.ps1 +1 -1
- package/scripts/install.sh +1 -1
- package/scripts/verify.js +327 -14
- package/scripts/verify.sh +10 -0
- package/src/catalog/loader.js +117 -0
- package/src/cli/args.js +118 -0
- package/src/cli/help.js +60 -0
- package/src/cli/main.js +6263 -0
- package/src/core/globals.js +52 -0
- package/src/core/hashes.js +15 -0
- package/src/core/policy.js +44 -0
- package/src/core/security.js +61 -0
- package/src/core/yaml.js +136 -0
- package/src/plugin/manifest.js +95 -0
- package/src/registry/provenance.js +114 -0
- package/src/registry/signing.js +392 -0
- package/src/registry/sources.js +40 -0
- package/src/registry/trust-store.js +41 -0
- package/src/registry/validation.js +45 -0
- package/src/registry/verdict.js +51 -0
- package/tests/README.md +37 -0
- package/tests/fixtures/README.md +22 -0
- package/tests/fixtures/custom-template-example/README.md +10 -0
- package/tests/fixtures/proposals/approved-append-line.md +28 -0
- package/tests/fixtures/proposals/approved-create-file.md +29 -0
- package/tests/fixtures/proposals/approved-replace-text.md +30 -0
- package/tests/fixtures/proposals/existing-create-file-no-overwrite.md +29 -0
- package/tests/fixtures/proposals/no-operations.md +18 -0
- package/tests/fixtures/proposals/path-traversal.md +29 -0
- package/tests/fixtures/proposals/pending-proposal.md +29 -0
- package/tests/fixtures/proposals/protected-path.md +29 -0
- package/tests/fixtures/proposals/replace-multiple-without-allow.md +30 -0
- package/tests/fixtures/registry-overrides/README.md +20 -0
- package/tests/fixtures/signed-registries/README.md +4 -0
- package/tests/fixtures/signed-registries/revoked-key/catalog.yaml +8 -0
- package/tests/fixtures/signed-registries/revoked-key/expected-verdict.json +7 -0
- package/tests/fixtures/signed-registries/revoked-key/registry-manifest.yaml +14 -0
- package/tests/fixtures/signed-registries/tampered-manifest/catalog.yaml +8 -0
- package/tests/fixtures/signed-registries/tampered-manifest/expected-verdict.json +7 -0
- package/tests/fixtures/signed-registries/tampered-manifest/registry-manifest.yaml +14 -0
- package/tests/fixtures/signed-registries/trusted-keys.yaml +23 -0
- package/tests/fixtures/signed-registries/unsigned-remote-required/catalog.yaml +8 -0
- package/tests/fixtures/signed-registries/unsigned-remote-required/expected-verdict.json +7 -0
- package/tests/fixtures/signed-registries/unsigned-remote-required/registry-manifest.yaml +9 -0
- package/tests/fixtures/signed-registries/unsupported-algorithm/catalog.yaml +8 -0
- package/tests/fixtures/signed-registries/unsupported-algorithm/expected-verdict.json +7 -0
- package/tests/fixtures/signed-registries/unsupported-algorithm/registry-manifest.yaml +14 -0
- package/tests/fixtures/signed-registries/valid-signed-registry/catalog.yaml +8 -0
- package/tests/fixtures/signed-registries/valid-signed-registry/expected-verdict.json +7 -0
- package/tests/fixtures/signed-registries/valid-signed-registry/registry-manifest.yaml +14 -0
- package/tests/fixtures/signed-registries/wrong-key/catalog.yaml +8 -0
- package/tests/fixtures/signed-registries/wrong-key/expected-verdict.json +7 -0
- package/tests/fixtures/signed-registries/wrong-key/registry-manifest.yaml +14 -0
- package/tests/smoke/README.md +37 -0
- package/tests/smoke/cli-smoke.md +49 -0
- package/tests/unit/build-output.test.js +40 -0
- package/tests/unit/catalog-loader.test.js +44 -0
- package/tests/unit/path-safety.test.js +62 -0
- package/tests/unit/plugin-manifest.test.js +94 -0
- package/tests/unit/prepublish-guard.test.js +35 -0
- package/tests/unit/registry-e2e-signature-fixtures.test.js +288 -0
- package/tests/unit/registry-policy.test.js +52 -0
- package/tests/unit/registry-provenance.test.js +185 -0
- package/tests/unit/registry-public-signing.test.js +109 -0
- package/tests/unit/registry-signature-policy.test.js +100 -0
- package/tests/unit/registry-signing.test.js +193 -0
- package/tests/unit/registry-trust-store.test.js +133 -0
- package/tests/unit/registry-url-validation.test.js +64 -0
- package/tests/unit/yaml.test.js +92 -0
package/scripts/verify.js
CHANGED
|
@@ -189,6 +189,24 @@ checkFile('scripts/pack-template.sh');
|
|
|
189
189
|
checkFile('scripts/prepublish-guard.js');
|
|
190
190
|
checkFile('bin/multimodel-dev-os.js');
|
|
191
191
|
|
|
192
|
+
// --- Modular Source Files ---
|
|
193
|
+
console.log('\nModular Source Files:');
|
|
194
|
+
checkFile('src/cli/main.js');
|
|
195
|
+
checkFile('src/cli/args.js');
|
|
196
|
+
checkFile('src/cli/help.js');
|
|
197
|
+
checkFile('src/core/yaml.js');
|
|
198
|
+
checkFile('src/core/hashes.js');
|
|
199
|
+
checkFile('src/core/policy.js');
|
|
200
|
+
checkFile('src/core/security.js');
|
|
201
|
+
checkFile('src/core/globals.js');
|
|
202
|
+
checkFile('src/registry/validation.js');
|
|
203
|
+
checkFile('src/registry/sources.js');
|
|
204
|
+
checkFile('src/registry/provenance.js');
|
|
205
|
+
checkFile('src/registry/signing.js');
|
|
206
|
+
checkFile('src/registry/trust-store.js');
|
|
207
|
+
checkFile('src/catalog/loader.js');
|
|
208
|
+
checkFile('src/plugin/manifest.js');
|
|
209
|
+
|
|
192
210
|
// --- GitHub Integration ---
|
|
193
211
|
console.log('\nGitHub Workflows:');
|
|
194
212
|
checkFile('.github/workflows/verify.yml');
|
|
@@ -231,6 +249,8 @@ checkFile('docs/registry-contribution.md');
|
|
|
231
249
|
checkFile('docs/v2-migration.md');
|
|
232
250
|
checkFile('docs/v2-release-checklist.md');
|
|
233
251
|
checkFile('docs/package-safety.md');
|
|
252
|
+
checkFile('docs/registry-signing.md');
|
|
253
|
+
checkFile('docs/registry-trust-store.md');
|
|
234
254
|
|
|
235
255
|
// --- v2.1.0 Intelligence Layer Documentation ---
|
|
236
256
|
console.log('\nIntelligence Layer Documentation:');
|
|
@@ -265,6 +285,7 @@ console.log('\nJSON Schemas:');
|
|
|
265
285
|
checkFile('.ai/schema/config.schema.json');
|
|
266
286
|
checkFile('.ai/schema/template.schema.json');
|
|
267
287
|
checkFile('.ai/schema/adapter.schema.json');
|
|
288
|
+
checkFile('.ai/schema/trusted-keys.schema.json');
|
|
268
289
|
|
|
269
290
|
// --- v2.1.0 Intelligence Layer (Schemas, Policies, Registries) ---
|
|
270
291
|
console.log('\nIntelligence Layer Schemas:');
|
|
@@ -287,8 +308,25 @@ console.log('\nIntelligence Layer Registries:');
|
|
|
287
308
|
checkFile('.ai/registries/capabilities.yaml');
|
|
288
309
|
checkFile('.ai/registries/tools.yaml');
|
|
289
310
|
checkFile('.ai/registries/workflows.yaml');
|
|
290
|
-
|
|
291
|
-
|
|
311
|
+
checkFile('.ai/registries/trusted-keys.yaml');
|
|
312
|
+
|
|
313
|
+
// --- Unit Tests ---
|
|
314
|
+
console.log('\nUnit Tests:');
|
|
315
|
+
checkFile('tests/unit/yaml.test.js');
|
|
316
|
+
checkFile('tests/unit/registry-url-validation.test.js');
|
|
317
|
+
checkFile('tests/unit/registry-policy.test.js');
|
|
318
|
+
checkFile('tests/unit/registry-provenance.test.js');
|
|
319
|
+
checkFile('tests/unit/registry-signing.test.js');
|
|
320
|
+
checkFile('tests/unit/registry-public-signing.test.js');
|
|
321
|
+
checkFile('tests/unit/registry-trust-store.test.js');
|
|
322
|
+
checkFile('tests/unit/registry-signature-policy.test.js');
|
|
323
|
+
checkFile('tests/unit/path-safety.test.js');
|
|
324
|
+
checkFile('tests/unit/plugin-manifest.test.js');
|
|
325
|
+
checkFile('tests/unit/catalog-loader.test.js');
|
|
326
|
+
checkFile('tests/unit/build-output.test.js');
|
|
327
|
+
checkFile('tests/unit/prepublish-guard.test.js');
|
|
328
|
+
|
|
329
|
+
// --- Test Manuals & Fixtures ---
|
|
292
330
|
console.log('\nTest Manuals:');
|
|
293
331
|
checkFile('tests/README.md');
|
|
294
332
|
checkFile('tests/fixtures/README.md');
|
|
@@ -549,7 +587,7 @@ try {
|
|
|
549
587
|
}
|
|
550
588
|
}
|
|
551
589
|
|
|
552
|
-
// Test 2: Allows version 3.
|
|
590
|
+
// Test 2: Allows version 3.5.0 with MMDO_ALLOW_PUBLISH=true
|
|
553
591
|
try {
|
|
554
592
|
const output = execSync('node scripts/prepublish-guard.js', {
|
|
555
593
|
cwd: projectRoot,
|
|
@@ -557,7 +595,7 @@ try {
|
|
|
557
595
|
encoding: 'utf8'
|
|
558
596
|
});
|
|
559
597
|
if (output.includes('Prepublish guard passed')) {
|
|
560
|
-
console.log(` ${GREEN}✓${NC} prepublish guard allows version 3.
|
|
598
|
+
console.log(` ${GREEN}✓${NC} prepublish guard allows version 3.5.0 when MMDO_ALLOW_PUBLISH=true`);
|
|
561
599
|
pass++;
|
|
562
600
|
} else {
|
|
563
601
|
console.error(` ${RED}✗${NC} prepublish guard passed but stdout missing success indicator`);
|
|
@@ -565,7 +603,7 @@ try {
|
|
|
565
603
|
}
|
|
566
604
|
} catch (err) {
|
|
567
605
|
const errText = err.stderr ? err.stderr.toString() : '';
|
|
568
|
-
console.error(` ${RED}✗${NC} prepublish guard blocked version 3.
|
|
606
|
+
console.error(` ${RED}✗${NC} prepublish guard blocked version 3.5.0: ${errText || err.message}`);
|
|
569
607
|
fail++;
|
|
570
608
|
}
|
|
571
609
|
|
|
@@ -579,12 +617,12 @@ try {
|
|
|
579
617
|
pass++;
|
|
580
618
|
}
|
|
581
619
|
|
|
582
|
-
// Test 4: Package.json version is exactly 3.
|
|
583
|
-
if (expectedVersion === '3.
|
|
584
|
-
console.log(` ${GREEN}✓${NC} package.json version is exactly 3.
|
|
620
|
+
// Test 4: Package.json version is exactly 3.5.0
|
|
621
|
+
if (expectedVersion === '3.5.0') {
|
|
622
|
+
console.log(` ${GREEN}✓${NC} package.json version is exactly 3.5.0`);
|
|
585
623
|
pass++;
|
|
586
624
|
} else {
|
|
587
|
-
console.error(` ${RED}✗${NC} package.json version is not 3.
|
|
625
|
+
console.error(` ${RED}✗${NC} package.json version is not 3.5.0 (found ${expectedVersion})`);
|
|
588
626
|
fail++;
|
|
589
627
|
}
|
|
590
628
|
} catch (e) {
|
|
@@ -592,6 +630,52 @@ try {
|
|
|
592
630
|
fail++;
|
|
593
631
|
}
|
|
594
632
|
|
|
633
|
+
// --- Post-build Generated CLI Checks ---
|
|
634
|
+
console.log('\nPost-build Generated CLI Checks:');
|
|
635
|
+
try {
|
|
636
|
+
// 0. Check build freshness
|
|
637
|
+
try {
|
|
638
|
+
execSync('node scripts/check-build-fresh.js', { cwd: projectRoot, stdio: 'ignore' });
|
|
639
|
+
console.log(` ${GREEN}✓${NC} generated bin matches current source layout`);
|
|
640
|
+
pass++;
|
|
641
|
+
} catch (err) {
|
|
642
|
+
console.error(` ${RED}✗${NC} generated bin is stale! Run 'npm run build' and commit bin/multimodel-dev-os.js`);
|
|
643
|
+
fail++;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const buildPath = join(projectRoot, 'bin', 'multimodel-dev-os.js');
|
|
647
|
+
const binContent = readFileSync(buildPath, 'utf8');
|
|
648
|
+
|
|
649
|
+
const totalShebangs = (binContent.match(/#!/g) || []).length;
|
|
650
|
+
if (binContent.startsWith('#!/usr/bin/env node') && totalShebangs === 1) {
|
|
651
|
+
console.log(` ${GREEN}✓${NC} generated bin has exactly one shebang at the top`);
|
|
652
|
+
pass++;
|
|
653
|
+
} else {
|
|
654
|
+
console.error(` ${RED}✗${NC} generated bin has invalid shebang layout (count: ${totalShebangs})`);
|
|
655
|
+
fail++;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
if (binContent.includes('// Generated from src/. Do not edit directly.')) {
|
|
659
|
+
console.log(` ${GREEN}✓${NC} generated bin has warning header`);
|
|
660
|
+
pass++;
|
|
661
|
+
} else {
|
|
662
|
+
console.error(` ${RED}✗${NC} generated bin is missing the warning header`);
|
|
663
|
+
fail++;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const hasUnsafeSync = binContent.includes("mod.get('${targetUrl}'") || (binContent.includes('execSync(`node -e "') && binContent.includes('${targetUrl}'));
|
|
667
|
+
if (!hasUnsafeSync && binContent.includes('execFileSync(process.execPath')) {
|
|
668
|
+
console.log(` ${GREEN}✓${NC} generated bin is free of unsafe URL interpolation and uses execFileSync`);
|
|
669
|
+
pass++;
|
|
670
|
+
} else {
|
|
671
|
+
console.error(` ${RED}✗${NC} generated bin fails safety scan (unsafe interpolation found)`);
|
|
672
|
+
fail++;
|
|
673
|
+
}
|
|
674
|
+
} catch (e) {
|
|
675
|
+
console.error(` ${RED}✗${NC} post-build generated CLI checks failed: ${e.message}`);
|
|
676
|
+
fail++;
|
|
677
|
+
}
|
|
678
|
+
|
|
595
679
|
// --- v2.8.0 / v2.8.1 Dashboard & Plugin Tests ---
|
|
596
680
|
console.log('\nRunning TUI Dashboard & Plugin Pre-Flight Tests...');
|
|
597
681
|
|
|
@@ -921,22 +1005,77 @@ try {
|
|
|
921
1005
|
fail++;
|
|
922
1006
|
}
|
|
923
1007
|
|
|
924
|
-
// Verify npm pack dry-run shows current version dynamically
|
|
1008
|
+
// Verify npm pack dry-run shows current version dynamically and has clean hygiene
|
|
925
1009
|
try {
|
|
926
|
-
const packOutput = execSync('npm pack --dry-run', { cwd: projectRoot, encoding: 'utf8'
|
|
927
|
-
|
|
1010
|
+
const packOutput = execSync('npm pack --dry-run 2>&1', { cwd: projectRoot, encoding: 'utf8' });
|
|
1011
|
+
const combinedOutput = packOutput;
|
|
1012
|
+
|
|
1013
|
+
const hasVersion = combinedOutput.includes(`multimodel-dev-os@${expectedVersion}`) || combinedOutput.includes(`multimodel-dev-os-${expectedVersion}.tgz`) || combinedOutput.includes(`version: ${expectedVersion}`);
|
|
1014
|
+
if (hasVersion) {
|
|
928
1015
|
console.log(` ${GREEN}✓${NC} npm pack --dry-run reports version ${expectedVersion}`);
|
|
929
1016
|
pass++;
|
|
930
1017
|
} else {
|
|
931
|
-
console.error(` ${RED}✗${NC} npm pack --dry-run did not report ${expectedVersion} in
|
|
1018
|
+
console.error(` ${RED}✗${NC} npm pack --dry-run did not report ${expectedVersion} in output`);
|
|
1019
|
+
fail++;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// Hygiene checks
|
|
1023
|
+
const lines = combinedOutput.split('\n');
|
|
1024
|
+
const files = lines
|
|
1025
|
+
.filter(l => l.includes('npm notice') && !l.includes('Tarball Details') && !l.includes('Tarball Filename') && !l.includes('package size:') && !l.includes('unpacked size:') && !l.includes('shasum:') && !l.includes('integrity:') && !l.includes('total files:'))
|
|
1026
|
+
.map(l => {
|
|
1027
|
+
const match = l.match(/npm notice\s+\d+(\.\d+)?[a-zA-Z]+\s+(.+)$/);
|
|
1028
|
+
return match ? match[2].trim() : '';
|
|
1029
|
+
})
|
|
1030
|
+
.filter(f => f !== '');
|
|
1031
|
+
|
|
1032
|
+
const hasSrc = files.some(f => f.startsWith('src/'));
|
|
1033
|
+
const hasTests = files.some(f => f.startsWith('tests/'));
|
|
1034
|
+
|
|
1035
|
+
if (hasSrc && hasTests) {
|
|
1036
|
+
console.log(` ${GREEN}✓${NC} npm pack includes 'src/' and 'tests/' directories`);
|
|
1037
|
+
pass++;
|
|
1038
|
+
} else {
|
|
1039
|
+
console.error(` ${RED}✗${NC} npm pack is missing 'src/' or 'tests/' directory`);
|
|
1040
|
+
fail++;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
const hasBlacklisted = files.some(f => f.includes('.npmrc') || f.includes('.env') || f.includes('node_modules') || f.endsWith('.tgz') || f.includes('coverage/'));
|
|
1044
|
+
if (!hasBlacklisted) {
|
|
1045
|
+
console.log(` ${GREEN}✓${NC} npm pack excludes sensitive and temporary files (.npmrc, .env, node_modules, .tgz, coverage)`);
|
|
1046
|
+
pass++;
|
|
1047
|
+
} else {
|
|
1048
|
+
console.error(` ${RED}✗${NC} npm pack contains blacklisted files!`);
|
|
932
1049
|
fail++;
|
|
933
1050
|
}
|
|
934
1051
|
} catch (e) {
|
|
935
1052
|
const stdErrOut = e.stderr ? e.stderr.toString() : '';
|
|
936
1053
|
const stdOutOut = e.stdout ? e.stdout.toString() : '';
|
|
937
|
-
|
|
1054
|
+
const combined = stdErrOut + '\n' + stdOutOut;
|
|
1055
|
+
|
|
1056
|
+
const hasVersion = combined.includes(`multimodel-dev-os@${expectedVersion}`) || combined.includes(`multimodel-dev-os-${expectedVersion}.tgz`) || combined.includes(`version: ${expectedVersion}`);
|
|
1057
|
+
if (hasVersion) {
|
|
938
1058
|
console.log(` ${GREEN}✓${NC} npm pack --dry-run reports version ${expectedVersion}`);
|
|
939
1059
|
pass++;
|
|
1060
|
+
|
|
1061
|
+
const hasSrc = combined.includes('src/') || combined.includes('src\\');
|
|
1062
|
+
const hasTests = combined.includes('tests/') || combined.includes('tests\\');
|
|
1063
|
+
if (hasSrc && hasTests) {
|
|
1064
|
+
console.log(` ${GREEN}✓${NC} npm pack includes 'src/' and 'tests/' directories`);
|
|
1065
|
+
pass++;
|
|
1066
|
+
} else {
|
|
1067
|
+
console.error(` ${RED}✗${NC} npm pack is missing 'src/' or 'tests/' directory`);
|
|
1068
|
+
fail++;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
const hasBlacklisted = combined.includes('.npmrc') || combined.includes('.env') || combined.includes('node_modules') || combined.includes('.tgz') || combined.includes('coverage/');
|
|
1072
|
+
if (!hasBlacklisted) {
|
|
1073
|
+
console.log(` ${GREEN}✓${NC} npm pack excludes sensitive and temporary files`);
|
|
1074
|
+
pass++;
|
|
1075
|
+
} else {
|
|
1076
|
+
console.error(` ${RED}✗${NC} npm pack contains blacklisted files!`);
|
|
1077
|
+
fail++;
|
|
1078
|
+
}
|
|
940
1079
|
} else {
|
|
941
1080
|
console.error(` ${RED}✗${NC} npm pack --dry-run failed or did not report ${expectedVersion}: ${e.message}`);
|
|
942
1081
|
fail++;
|
|
@@ -1271,6 +1410,180 @@ const checkExamplesHygiene = (dir) => {
|
|
|
1271
1410
|
};
|
|
1272
1411
|
checkExamplesHygiene(join(projectRoot, 'examples'));
|
|
1273
1412
|
|
|
1413
|
+
// --- Registry Signing & Provenance Checks ---
|
|
1414
|
+
console.log('\nRegistry Signing & Provenance Checks:');
|
|
1415
|
+
|
|
1416
|
+
// Check .gitignore contains registry-signing-key
|
|
1417
|
+
try {
|
|
1418
|
+
const gitignoreContent = readFileSync(join(projectRoot, '.gitignore'), 'utf8');
|
|
1419
|
+
if (gitignoreContent.includes('registry-signing-key')) {
|
|
1420
|
+
console.log(` ${GREEN}✓${NC} .gitignore includes registry-signing-key pattern`);
|
|
1421
|
+
pass++;
|
|
1422
|
+
} else {
|
|
1423
|
+
console.error(` ${RED}✗${NC} .gitignore is missing the registry-signing-key entry (secrets must be gitignored)`);
|
|
1424
|
+
fail++;
|
|
1425
|
+
}
|
|
1426
|
+
} catch (e) {
|
|
1427
|
+
console.error(` ${RED}✗${NC} Failed to read .gitignore: ${e.message}`);
|
|
1428
|
+
fail++;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
// Check provenance.js exports the expected API surface
|
|
1432
|
+
try {
|
|
1433
|
+
const provenanceSrc = readFileSync(join(projectRoot, 'src', 'registry', 'provenance.js'), 'utf8');
|
|
1434
|
+
const hasLoadLockfile = provenanceSrc.includes('export function loadRegistryLockfile');
|
|
1435
|
+
const hasSaveLockfile = provenanceSrc.includes('export function saveRegistryLockfile');
|
|
1436
|
+
const hasUpdateEntry = provenanceSrc.includes('export function updateLockfileEntry');
|
|
1437
|
+
const hasGetPath = provenanceSrc.includes('export function getLockfilePath');
|
|
1438
|
+
if (hasLoadLockfile && hasSaveLockfile && hasUpdateEntry && hasGetPath) {
|
|
1439
|
+
console.log(` ${GREEN}✓${NC} src/registry/provenance.js exports complete API (load/save/update/getPath)`);
|
|
1440
|
+
pass++;
|
|
1441
|
+
} else {
|
|
1442
|
+
console.error(` ${RED}✗${NC} src/registry/provenance.js is missing expected exports`);
|
|
1443
|
+
fail++;
|
|
1444
|
+
}
|
|
1445
|
+
} catch (e) {
|
|
1446
|
+
console.error(` ${RED}✗${NC} Failed to check provenance.js: ${e.message}`);
|
|
1447
|
+
fail++;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
// Check signing.js exports the expected API surface
|
|
1451
|
+
try {
|
|
1452
|
+
const signingSrc = readFileSync(join(projectRoot, 'src', 'registry', 'signing.js'), 'utf8');
|
|
1453
|
+
const hasLoadKey = signingSrc.includes('export function loadSigningKey');
|
|
1454
|
+
const hasGenKey = signingSrc.includes('export function generateSigningKey');
|
|
1455
|
+
const hasSaveKey = signingSrc.includes('export function saveSigningKey');
|
|
1456
|
+
const hasSign = signingSrc.includes('export function signPayload');
|
|
1457
|
+
const hasVerify = signingSrc.includes('export function verifySignature');
|
|
1458
|
+
const hasTimingSafe = signingSrc.includes('timingSafeEqual');
|
|
1459
|
+
const hasEdKeygen = signingSrc.includes('export function generateEd25519KeyPair');
|
|
1460
|
+
const hasEdSign = signingSrc.includes('export function signEd25519Payload');
|
|
1461
|
+
const hasEdVerify = signingSrc.includes('export function verifyEd25519Payload');
|
|
1462
|
+
const hasSigBlockVerify = signingSrc.includes('export function verifySignatureBlock');
|
|
1463
|
+
|
|
1464
|
+
if (hasLoadKey && hasGenKey && hasSaveKey && hasSign && hasVerify && hasTimingSafe && hasEdKeygen && hasEdSign && hasEdVerify && hasSigBlockVerify) {
|
|
1465
|
+
console.log(` ${GREEN}✓${NC} src/registry/signing.js exports complete API (HMAC + Ed25519)`);
|
|
1466
|
+
pass++;
|
|
1467
|
+
} else {
|
|
1468
|
+
console.error(` ${RED}✗${NC} src/registry/signing.js is missing expected exports`);
|
|
1469
|
+
fail++;
|
|
1470
|
+
}
|
|
1471
|
+
} catch (e) {
|
|
1472
|
+
console.error(` ${RED}✗${NC} Failed to check signing.js: ${e.message}`);
|
|
1473
|
+
fail++;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
// Check trust-store.js exports expected API surface
|
|
1477
|
+
try {
|
|
1478
|
+
const trustSrc = readFileSync(join(projectRoot, 'src', 'registry', 'trust-store.js'), 'utf8');
|
|
1479
|
+
const hasLoadTrustedKeys = trustSrc.includes('export function loadTrustedKeys');
|
|
1480
|
+
if (hasLoadTrustedKeys) {
|
|
1481
|
+
console.log(` ${GREEN}✓${NC} src/registry/trust-store.js exports loadTrustedKeys`);
|
|
1482
|
+
pass++;
|
|
1483
|
+
} else {
|
|
1484
|
+
console.error(` ${RED}✗${NC} src/registry/trust-store.js is missing expected exports`);
|
|
1485
|
+
fail++;
|
|
1486
|
+
}
|
|
1487
|
+
} catch (e) {
|
|
1488
|
+
console.error(` ${RED}✗${NC} Failed to check trust-store.js: ${e.message}`);
|
|
1489
|
+
fail++;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
// Check main.js imports the new modules
|
|
1493
|
+
try {
|
|
1494
|
+
const mainSrc = readFileSync(join(projectRoot, 'src', 'cli', 'main.js'), 'utf8');
|
|
1495
|
+
const hasProvenanceImport = mainSrc.includes("from '../registry/provenance.js'");
|
|
1496
|
+
const hasSigningImport = mainSrc.includes("from '../registry/signing.js'");
|
|
1497
|
+
const hasTrustImport = mainSrc.includes("from '../registry/trust-store.js'");
|
|
1498
|
+
const hasKeygenHandler = mainSrc.includes('handleRegistryKeygen');
|
|
1499
|
+
const hasLockHandler = mainSrc.includes('handleRegistryLock');
|
|
1500
|
+
const hasTrustHandler = mainSrc.includes('handleRegistryTrustList');
|
|
1501
|
+
if (hasProvenanceImport && hasSigningImport && hasTrustImport && hasKeygenHandler && hasLockHandler && hasTrustHandler) {
|
|
1502
|
+
console.log(` ${GREEN}✓${NC} src/cli/main.js imports provenance/signing/trust-store and registers handlers`);
|
|
1503
|
+
pass++;
|
|
1504
|
+
} else {
|
|
1505
|
+
console.error(` ${RED}✗${NC} src/cli/main.js is missing required imports or handlers`);
|
|
1506
|
+
fail++;
|
|
1507
|
+
}
|
|
1508
|
+
} catch (e) {
|
|
1509
|
+
console.error(` ${RED}✗${NC} Failed to check main.js integrations: ${e.message}`);
|
|
1510
|
+
fail++;
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
// Check that policy.js has the new fields in defaults
|
|
1514
|
+
try {
|
|
1515
|
+
const policySrc = readFileSync(join(projectRoot, 'src', 'core', 'policy.js'), 'utf8');
|
|
1516
|
+
const hasLockfileField = policySrc.includes('require_lockfile_on_verify');
|
|
1517
|
+
const hasUnsignedLocal = policySrc.includes('allow_unsigned_local');
|
|
1518
|
+
const hasUnsignedBundled = policySrc.includes('allow_unsigned_bundled');
|
|
1519
|
+
const hasUnsignedRemote = policySrc.includes('allow_unsigned_remote');
|
|
1520
|
+
const hasTrustedKeysFile = policySrc.includes('trusted_keys_file');
|
|
1521
|
+
const hasAllowedAlgs = policySrc.includes('allowed_signature_algorithms');
|
|
1522
|
+
const hasRequireTrustedPublisher = policySrc.includes('require_trusted_publisher');
|
|
1523
|
+
const hasProvenanceRequired = policySrc.includes('provenance_required');
|
|
1524
|
+
|
|
1525
|
+
if (hasLockfileField && hasUnsignedLocal && hasUnsignedBundled && hasUnsignedRemote && hasTrustedKeysFile && hasAllowedAlgs && hasRequireTrustedPublisher && hasProvenanceRequired) {
|
|
1526
|
+
console.log(` ${GREEN}✓${NC} src/core/policy.js includes all Sprint 2 policy defaults`);
|
|
1527
|
+
pass++;
|
|
1528
|
+
} else {
|
|
1529
|
+
console.error(` ${RED}✗${NC} src/core/policy.js is missing required policy defaults`);
|
|
1530
|
+
fail++;
|
|
1531
|
+
}
|
|
1532
|
+
} catch (e) {
|
|
1533
|
+
console.error(` ${RED}✗${NC} Failed to check policy.js: ${e.message}`);
|
|
1534
|
+
fail++;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
// --- v3.5.0 Sprint 3 E2E Fixtures & Threat Model Checks ---
|
|
1538
|
+
console.log('\nSprint 3 Signed Registry E2E & Readiness Checks:');
|
|
1539
|
+
checkFile('src/registry/verdict.js');
|
|
1540
|
+
checkFile('tests/unit/registry-e2e-signature-fixtures.test.js');
|
|
1541
|
+
checkFile('docs/security-threat-model.md');
|
|
1542
|
+
checkFile('docs/v3.5.0-readiness.md');
|
|
1543
|
+
|
|
1544
|
+
// Verify that the trusted-keys.yaml in the E2E fixtures directory exists
|
|
1545
|
+
const e2eKeysPath = 'tests/fixtures/signed-registries/trusted-keys.yaml';
|
|
1546
|
+
if (checkFile(e2eKeysPath)) {
|
|
1547
|
+
const e2eKeysContent = readFileSync(join(projectRoot, e2eKeysPath), 'utf8');
|
|
1548
|
+
if (e2eKeysContent.includes('test-key-valid') && e2eKeysContent.includes('test-key-revoked')) {
|
|
1549
|
+
console.log(` ${GREEN}✓${NC} ${e2eKeysPath} is populated with test fixtures and marked for testing`);
|
|
1550
|
+
pass++;
|
|
1551
|
+
} else {
|
|
1552
|
+
console.error(` ${RED}✗${NC} ${e2eKeysPath} is missing expected test keys`);
|
|
1553
|
+
fail++;
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
// Verify that the threat model document has a standard threat modeling structure
|
|
1558
|
+
try {
|
|
1559
|
+
const threatModelContent = readFileSync(join(projectRoot, 'docs/security-threat-model.md'), 'utf8');
|
|
1560
|
+
if (threatModelContent.includes('Threat Model') && (threatModelContent.includes('STRIDE') || threatModelContent.includes('stride'))) {
|
|
1561
|
+
console.log(` ${GREEN}✓${NC} docs/security-threat-model.md structure verified`);
|
|
1562
|
+
pass++;
|
|
1563
|
+
} else {
|
|
1564
|
+
console.error(` ${RED}✗${NC} docs/security-threat-model.md is missing standard threat modeling structure`);
|
|
1565
|
+
fail++;
|
|
1566
|
+
}
|
|
1567
|
+
} catch (e) {
|
|
1568
|
+
console.error(` ${RED}✗${NC} Failed to verify threat model document: ${e.message}`);
|
|
1569
|
+
fail++;
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
// Verify that no private keys are committed in main directories (like .ai/)
|
|
1573
|
+
try {
|
|
1574
|
+
const rootKeyFile = '.ai/registry-signing-key';
|
|
1575
|
+
if (existsSync(join(projectRoot, rootKeyFile))) {
|
|
1576
|
+
console.error(` ${RED}✗${NC} Private signing key ${rootKeyFile} should not be committed!`);
|
|
1577
|
+
fail++;
|
|
1578
|
+
} else {
|
|
1579
|
+
console.log(` ${GREEN}✓${NC} No private registry-signing-key found in codebase root`);
|
|
1580
|
+
pass++;
|
|
1581
|
+
}
|
|
1582
|
+
} catch (e) {
|
|
1583
|
+
console.error(` ${RED}✗${NC} Failed to check private key existence: ${e.message}`);
|
|
1584
|
+
fail++;
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1274
1587
|
console.log('\n=====================================================');
|
|
1275
1588
|
const total = pass + fail + warn;
|
|
1276
1589
|
console.log(` Pass: ${GREEN}${pass}${NC} Fail: ${RED}${fail}${NC} Warn: ${YELLOW}${warn}${NC} Total: ${total}`);
|
package/scripts/verify.sh
CHANGED
|
@@ -192,6 +192,8 @@ check_file "docs/cli-roadmap.md"
|
|
|
192
192
|
check_file "docs/faq.md"
|
|
193
193
|
check_file "docs/testing.md"
|
|
194
194
|
check_file "docs/npm-publishing.md"
|
|
195
|
+
check_file "docs/release-policy.md"
|
|
196
|
+
check_file "docs/package-safety.md"
|
|
195
197
|
|
|
196
198
|
# --- CLI & Packaging Pre-Flight Tests ---
|
|
197
199
|
echo ""
|
|
@@ -235,6 +237,14 @@ else
|
|
|
235
237
|
PASS=$((PASS + 1))
|
|
236
238
|
fi
|
|
237
239
|
|
|
240
|
+
if ! npm run check:build >/dev/null; then
|
|
241
|
+
echo -e " ${RED}✗${NC} Generated CLI is stale. Run npm run build."
|
|
242
|
+
FAIL=$((FAIL + 1))
|
|
243
|
+
else
|
|
244
|
+
echo -e " ${GREEN}✓${NC} Generated CLI is fresh"
|
|
245
|
+
PASS=$((PASS + 1))
|
|
246
|
+
fi
|
|
247
|
+
|
|
238
248
|
if ! node bin/multimodel-dev-os.js init --dry-run --force >/dev/null; then
|
|
239
249
|
echo -e " ${RED}✗${NC} node bin/multimodel-dev-os.js init --dry-run failed"
|
|
240
250
|
FAIL=$((FAIL + 1))
|
|
@@ -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
|
+
}
|
package/src/cli/args.js
ADDED
|
@@ -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
|
+
}
|