@omnidev-ai/cli 0.11.0 → 0.12.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/dist/index.js +589 -54
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -22,10 +22,10 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
22
22
|
import { run } from "@stricli/core";
|
|
23
23
|
|
|
24
24
|
// src/lib/dynamic-app.ts
|
|
25
|
-
import { existsSync as
|
|
25
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
26
26
|
import { createRequire as createRequire2 } from "node:module";
|
|
27
27
|
import { join as join9 } from "node:path";
|
|
28
|
-
import { buildApplication, buildRouteMap as
|
|
28
|
+
import { buildApplication, buildRouteMap as buildRouteMap6 } from "@stricli/core";
|
|
29
29
|
|
|
30
30
|
// src/commands/add.ts
|
|
31
31
|
import { existsSync as existsSync3 } from "node:fs";
|
|
@@ -357,11 +357,19 @@ async function runAddCap(flags, name) {
|
|
|
357
357
|
}
|
|
358
358
|
let capabilityId = name;
|
|
359
359
|
if (!capabilityId) {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
360
|
+
if (flags.path && sourceType === "github") {
|
|
361
|
+
const pathParts = flags.path.split("/").filter(Boolean);
|
|
362
|
+
capabilityId = pathParts[pathParts.length - 1];
|
|
363
|
+
if (!capabilityId) {
|
|
364
|
+
capabilityId = "capability";
|
|
365
|
+
}
|
|
366
|
+
} else {
|
|
367
|
+
const sourceValue = sourceType === "local" ? flags.local : flags.github;
|
|
368
|
+
if (!sourceValue) {
|
|
369
|
+
throw new Error("Unreachable: cannot infer capability ID");
|
|
370
|
+
}
|
|
371
|
+
capabilityId = await inferCapabilityId(sourceValue, sourceType);
|
|
363
372
|
}
|
|
364
|
-
capabilityId = await inferCapabilityId(sourceValue, sourceType);
|
|
365
373
|
console.log(` Inferred capability ID: ${capabilityId}`);
|
|
366
374
|
}
|
|
367
375
|
const config = await loadBaseConfig();
|
|
@@ -684,6 +692,7 @@ import {
|
|
|
684
692
|
generateSkillTemplate,
|
|
685
693
|
getEnabledCapabilities,
|
|
686
694
|
loadCapabilityConfig,
|
|
695
|
+
loadLockFile,
|
|
687
696
|
syncAgentConfiguration as syncAgentConfiguration2
|
|
688
697
|
} from "@omnidev-ai/core";
|
|
689
698
|
import { buildCommand as buildCommand2, buildRouteMap as buildRouteMap2 } from "@stricli/core";
|
|
@@ -694,10 +703,11 @@ function isValidCapabilityId(id) {
|
|
|
694
703
|
}
|
|
695
704
|
|
|
696
705
|
// src/commands/capability.ts
|
|
697
|
-
async function runCapabilityList() {
|
|
706
|
+
async function runCapabilityList(flags = {}) {
|
|
698
707
|
try {
|
|
699
708
|
const enabledIds = await getEnabledCapabilities();
|
|
700
709
|
const capabilityPaths = await discoverCapabilities();
|
|
710
|
+
const lockFile = await loadLockFile();
|
|
701
711
|
if (capabilityPaths.length === 0) {
|
|
702
712
|
console.log("No capabilities found.");
|
|
703
713
|
console.log("");
|
|
@@ -713,9 +723,29 @@ async function runCapabilityList() {
|
|
|
713
723
|
const isEnabled = enabledIds.includes(capConfig.capability.id);
|
|
714
724
|
const status = isEnabled ? "✓ enabled" : "✗ disabled";
|
|
715
725
|
const { id, name, version } = capConfig.capability;
|
|
726
|
+
const lockEntry = lockFile.capabilities[id];
|
|
716
727
|
console.log(` ${status} ${name}`);
|
|
717
728
|
console.log(` ID: ${id}`);
|
|
718
729
|
console.log(` Version: ${version}`);
|
|
730
|
+
if (flags.verbose && lockEntry) {
|
|
731
|
+
console.log(` Source: ${lockEntry.source}`);
|
|
732
|
+
if (lockEntry.version_source) {
|
|
733
|
+
console.log(` Version from: ${lockEntry.version_source}`);
|
|
734
|
+
}
|
|
735
|
+
if (lockEntry.commit) {
|
|
736
|
+
console.log(` Commit: ${lockEntry.commit.substring(0, 7)}`);
|
|
737
|
+
}
|
|
738
|
+
if (lockEntry.content_hash) {
|
|
739
|
+
console.log(` Content hash: ${lockEntry.content_hash.substring(0, 12)}`);
|
|
740
|
+
}
|
|
741
|
+
if (lockEntry.ref) {
|
|
742
|
+
console.log(` Ref: ${lockEntry.ref}`);
|
|
743
|
+
}
|
|
744
|
+
if (lockEntry.updated_at) {
|
|
745
|
+
const date = new Date(lockEntry.updated_at);
|
|
746
|
+
console.log(` Updated: ${date.toLocaleString()}`);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
719
749
|
console.log("");
|
|
720
750
|
} catch (error) {
|
|
721
751
|
console.error(` ✗ Failed to load capability at ${path}:`, error);
|
|
@@ -765,6 +795,65 @@ async function runCapabilityDisable(_flags, name) {
|
|
|
765
795
|
function toTitleCase(kebabCase) {
|
|
766
796
|
return kebabCase.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
767
797
|
}
|
|
798
|
+
function generatePackageJson(id) {
|
|
799
|
+
const pkg = {
|
|
800
|
+
name: `@capability/${id}`,
|
|
801
|
+
version: "0.1.0",
|
|
802
|
+
type: "module",
|
|
803
|
+
main: "dist/index.js",
|
|
804
|
+
scripts: {
|
|
805
|
+
build: "esbuild index.ts --bundle --platform=node --format=esm --outfile=dist/index.js",
|
|
806
|
+
clean: "rm -rf dist"
|
|
807
|
+
},
|
|
808
|
+
dependencies: {
|
|
809
|
+
"@omnidev-ai/core": "latest"
|
|
810
|
+
},
|
|
811
|
+
devDependencies: {
|
|
812
|
+
esbuild: "^0.20.0"
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
return JSON.stringify(pkg, null, "\t");
|
|
816
|
+
}
|
|
817
|
+
function generateIndexTs(id, name) {
|
|
818
|
+
return `/**
|
|
819
|
+
* ${name} Capability
|
|
820
|
+
*
|
|
821
|
+
* A programmatic capability with CLI commands.
|
|
822
|
+
*/
|
|
823
|
+
|
|
824
|
+
import type { CapabilityExport } from "@omnidev-ai/core";
|
|
825
|
+
import { buildCommand } from "@stricli/core";
|
|
826
|
+
|
|
827
|
+
// Example command implementation
|
|
828
|
+
async function run${id.replace(/-/g, "").replace(/^./, (c) => c.toUpperCase())}(): Promise<void> {
|
|
829
|
+
console.log("Hello from ${name}!");
|
|
830
|
+
console.log("");
|
|
831
|
+
console.log("This is a programmatic capability.");
|
|
832
|
+
console.log("Edit index.ts to customize this command.");
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// Build the main command
|
|
836
|
+
const ${id.replace(/-/g, "")}Command = buildCommand({
|
|
837
|
+
docs: {
|
|
838
|
+
brief: "${name} command",
|
|
839
|
+
},
|
|
840
|
+
parameters: {},
|
|
841
|
+
func: run${id.replace(/-/g, "").replace(/^./, (c) => c.toUpperCase())},
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
// Default export: Structured capability export
|
|
845
|
+
export default {
|
|
846
|
+
cliCommands: {
|
|
847
|
+
"${id}": ${id.replace(/-/g, "")}Command,
|
|
848
|
+
},
|
|
849
|
+
} satisfies CapabilityExport;
|
|
850
|
+
`;
|
|
851
|
+
}
|
|
852
|
+
function generateGitignore() {
|
|
853
|
+
return `dist/
|
|
854
|
+
node_modules/
|
|
855
|
+
`;
|
|
856
|
+
}
|
|
768
857
|
async function runCapabilityNew(flags, capabilityId) {
|
|
769
858
|
try {
|
|
770
859
|
if (!existsSync4(".omni")) {
|
|
@@ -809,6 +898,11 @@ async function runCapabilityNew(flags, capabilityId) {
|
|
|
809
898
|
mkdirSync4(hooksDir, { recursive: true });
|
|
810
899
|
await writeFile5(join8(hooksDir, "hooks.toml"), generateHooksTemplate(), "utf-8");
|
|
811
900
|
await writeFile5(join8(hooksDir, "example-hook.sh"), generateHookScript(), "utf-8");
|
|
901
|
+
if (flags.programmatic) {
|
|
902
|
+
await writeFile5(join8(capabilityDir, "package.json"), generatePackageJson(id), "utf-8");
|
|
903
|
+
await writeFile5(join8(capabilityDir, "index.ts"), generateIndexTs(id, name), "utf-8");
|
|
904
|
+
await writeFile5(join8(capabilityDir, ".gitignore"), generateGitignore(), "utf-8");
|
|
905
|
+
}
|
|
812
906
|
console.log(`✓ Created capability: ${name}`);
|
|
813
907
|
console.log(` Location: ${capabilityDir}`);
|
|
814
908
|
console.log("");
|
|
@@ -818,10 +912,26 @@ async function runCapabilityNew(flags, capabilityId) {
|
|
|
818
912
|
console.log(" - rules/coding-standards.md");
|
|
819
913
|
console.log(" - hooks/hooks.toml");
|
|
820
914
|
console.log(" - hooks/example-hook.sh");
|
|
915
|
+
if (flags.programmatic) {
|
|
916
|
+
console.log(" - package.json");
|
|
917
|
+
console.log(" - index.ts");
|
|
918
|
+
console.log(" - .gitignore");
|
|
919
|
+
}
|
|
821
920
|
console.log("");
|
|
822
|
-
|
|
823
|
-
|
|
921
|
+
if (flags.programmatic) {
|
|
922
|
+
console.log("\uD83D\uDCA1 To build and use this capability:");
|
|
923
|
+
console.log(` cd ${capabilityDir}`);
|
|
924
|
+
console.log(" npm install && npm run build");
|
|
925
|
+
console.log(` cd -`);
|
|
926
|
+
console.log(` omnidev add cap --local ./${capabilityDir}`);
|
|
927
|
+
} else {
|
|
928
|
+
console.log("\uD83D\uDCA1 To add this capability as a local source, run:");
|
|
929
|
+
console.log(` omnidev add cap --local ./${capabilityDir}`);
|
|
930
|
+
}
|
|
824
931
|
} catch (error) {
|
|
932
|
+
if (error instanceof Error && error.name === "ExitPromptError") {
|
|
933
|
+
process.exit(0);
|
|
934
|
+
}
|
|
825
935
|
console.error("Error creating capability:", error);
|
|
826
936
|
process.exit(1);
|
|
827
937
|
}
|
|
@@ -833,9 +943,12 @@ var newCommand = buildCommand2({
|
|
|
833
943
|
|
|
834
944
|
By default, creates the capability at capabilities/<id>. You can specify a custom path using the --path flag or interactively.
|
|
835
945
|
|
|
946
|
+
Use --programmatic to create a TypeScript capability with CLI commands, package.json, and esbuild setup.
|
|
947
|
+
|
|
836
948
|
Examples:
|
|
837
|
-
omnidev capability new my-cap
|
|
838
|
-
omnidev capability new my-cap --path ./caps/my
|
|
949
|
+
omnidev capability new my-cap # Prompts for path, defaults to capabilities/my-cap
|
|
950
|
+
omnidev capability new my-cap --path ./caps/my # Uses ./caps/my directly
|
|
951
|
+
omnidev capability new my-cap --programmatic # Creates with TypeScript + CLI support`
|
|
839
952
|
},
|
|
840
953
|
parameters: {
|
|
841
954
|
flags: {
|
|
@@ -844,6 +957,11 @@ Examples:
|
|
|
844
957
|
brief: "Output path for the capability (skips interactive prompt)",
|
|
845
958
|
parse: String,
|
|
846
959
|
optional: true
|
|
960
|
+
},
|
|
961
|
+
programmatic: {
|
|
962
|
+
kind: "boolean",
|
|
963
|
+
brief: "Create a TypeScript capability with CLI commands and esbuild setup",
|
|
964
|
+
optional: true
|
|
847
965
|
}
|
|
848
966
|
},
|
|
849
967
|
positional: {
|
|
@@ -863,11 +981,31 @@ Examples:
|
|
|
863
981
|
});
|
|
864
982
|
var listCommand = buildCommand2({
|
|
865
983
|
docs: {
|
|
866
|
-
brief: "List all discovered capabilities"
|
|
984
|
+
brief: "List all discovered capabilities",
|
|
985
|
+
fullDescription: `List all discovered capabilities with their status.
|
|
986
|
+
|
|
987
|
+
Use --verbose to show additional version information including:
|
|
988
|
+
- Source (git URL or file path)
|
|
989
|
+
- Where the version was detected from
|
|
990
|
+
- Commit hash (for git sources)
|
|
991
|
+
- Content hash (for file sources)
|
|
992
|
+
- Last update time
|
|
993
|
+
|
|
994
|
+
Examples:
|
|
995
|
+
omnidev capability list
|
|
996
|
+
omnidev capability list --verbose`
|
|
867
997
|
},
|
|
868
|
-
parameters: {
|
|
869
|
-
|
|
870
|
-
|
|
998
|
+
parameters: {
|
|
999
|
+
flags: {
|
|
1000
|
+
verbose: {
|
|
1001
|
+
kind: "boolean",
|
|
1002
|
+
brief: "Show detailed version information",
|
|
1003
|
+
optional: true
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
},
|
|
1007
|
+
async func(flags) {
|
|
1008
|
+
await runCapabilityList(flags);
|
|
871
1009
|
}
|
|
872
1010
|
});
|
|
873
1011
|
var enableCommand = buildCommand2({
|
|
@@ -1119,8 +1257,7 @@ import { buildCommand as buildCommand4 } from "@stricli/core";
|
|
|
1119
1257
|
// src/prompts/provider.ts
|
|
1120
1258
|
import { checkbox, confirm } from "@inquirer/prompts";
|
|
1121
1259
|
var PROVIDER_GITIGNORE_FILES = {
|
|
1122
|
-
claude: ["CLAUDE.md", ".claude/"],
|
|
1123
|
-
"claude-code": ["CLAUDE.md", ".claude/"],
|
|
1260
|
+
"claude-code": ["CLAUDE.md", ".claude/", ".mcp.json"],
|
|
1124
1261
|
cursor: [".cursor/"],
|
|
1125
1262
|
codex: ["AGENTS.md", ".codex/"],
|
|
1126
1263
|
opencode: [".opencode/"]
|
|
@@ -1549,17 +1686,398 @@ var providerRoutes = buildRouteMap4({
|
|
|
1549
1686
|
}
|
|
1550
1687
|
});
|
|
1551
1688
|
|
|
1552
|
-
// src/commands/
|
|
1689
|
+
// src/commands/security.ts
|
|
1553
1690
|
import { existsSync as existsSync8 } from "node:fs";
|
|
1691
|
+
import {
|
|
1692
|
+
addSecurityAllow,
|
|
1693
|
+
buildCapabilityRegistry,
|
|
1694
|
+
formatScanResults,
|
|
1695
|
+
getAllSecurityAllows,
|
|
1696
|
+
readSecurityAllows,
|
|
1697
|
+
removeSecurityAllow,
|
|
1698
|
+
scanCapabilities
|
|
1699
|
+
} from "@omnidev-ai/core";
|
|
1700
|
+
import { buildCommand as buildCommand7, buildRouteMap as buildRouteMap5 } from "@stricli/core";
|
|
1701
|
+
var VALID_FINDING_TYPES = [
|
|
1702
|
+
"unicode_bidi",
|
|
1703
|
+
"unicode_zero_width",
|
|
1704
|
+
"unicode_control",
|
|
1705
|
+
"symlink_escape",
|
|
1706
|
+
"symlink_absolute",
|
|
1707
|
+
"suspicious_script",
|
|
1708
|
+
"binary_file"
|
|
1709
|
+
];
|
|
1710
|
+
function isValidFindingType(type) {
|
|
1711
|
+
return VALID_FINDING_TYPES.includes(type);
|
|
1712
|
+
}
|
|
1713
|
+
async function filterAllowedFindings(summary, options = {}) {
|
|
1714
|
+
const state = await readSecurityAllows();
|
|
1715
|
+
const { includeLow = false } = options;
|
|
1716
|
+
const filteredResults = summary.results.map((result) => {
|
|
1717
|
+
const allows = state.allows[result.capabilityId] ?? [];
|
|
1718
|
+
const filteredFindings = result.findings.filter((finding) => {
|
|
1719
|
+
if (allows.includes(finding.type))
|
|
1720
|
+
return false;
|
|
1721
|
+
if (!includeLow && finding.severity === "low")
|
|
1722
|
+
return false;
|
|
1723
|
+
return true;
|
|
1724
|
+
});
|
|
1725
|
+
return {
|
|
1726
|
+
...result,
|
|
1727
|
+
findings: filteredFindings,
|
|
1728
|
+
passed: filteredFindings.length === 0 || filteredFindings.every((f) => f.severity === "low")
|
|
1729
|
+
};
|
|
1730
|
+
});
|
|
1731
|
+
const findingsByType = {
|
|
1732
|
+
unicode_bidi: 0,
|
|
1733
|
+
unicode_zero_width: 0,
|
|
1734
|
+
unicode_control: 0,
|
|
1735
|
+
symlink_escape: 0,
|
|
1736
|
+
symlink_absolute: 0,
|
|
1737
|
+
suspicious_script: 0,
|
|
1738
|
+
binary_file: 0
|
|
1739
|
+
};
|
|
1740
|
+
const findingsBySeverity = {
|
|
1741
|
+
low: 0,
|
|
1742
|
+
medium: 0,
|
|
1743
|
+
high: 0,
|
|
1744
|
+
critical: 0
|
|
1745
|
+
};
|
|
1746
|
+
let totalFindings = 0;
|
|
1747
|
+
let capabilitiesWithFindings = 0;
|
|
1748
|
+
for (const result of filteredResults) {
|
|
1749
|
+
if (result.findings.length > 0) {
|
|
1750
|
+
capabilitiesWithFindings++;
|
|
1751
|
+
}
|
|
1752
|
+
for (const finding of result.findings) {
|
|
1753
|
+
totalFindings++;
|
|
1754
|
+
const currentTypeCount = findingsByType[finding.type] ?? 0;
|
|
1755
|
+
findingsByType[finding.type] = currentTypeCount + 1;
|
|
1756
|
+
const currentSeverityCount = findingsBySeverity[finding.severity] ?? 0;
|
|
1757
|
+
findingsBySeverity[finding.severity] = currentSeverityCount + 1;
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
return {
|
|
1761
|
+
...summary,
|
|
1762
|
+
results: filteredResults,
|
|
1763
|
+
totalFindings,
|
|
1764
|
+
capabilitiesWithFindings,
|
|
1765
|
+
findingsByType,
|
|
1766
|
+
findingsBySeverity,
|
|
1767
|
+
allPassed: filteredResults.every((r) => r.passed)
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
function formatFindingsWithHints(summary) {
|
|
1771
|
+
const lines = [];
|
|
1772
|
+
lines.push("Security Scan Results");
|
|
1773
|
+
lines.push("=====================");
|
|
1774
|
+
lines.push("");
|
|
1775
|
+
if (summary.totalFindings === 0) {
|
|
1776
|
+
lines.push("No security issues found");
|
|
1777
|
+
return lines.join(`
|
|
1778
|
+
`);
|
|
1779
|
+
}
|
|
1780
|
+
lines.push(`Found ${summary.totalFindings} issue(s) in ${summary.capabilitiesWithFindings} capability(ies)`);
|
|
1781
|
+
lines.push("");
|
|
1782
|
+
if (summary.findingsBySeverity.critical > 0) {
|
|
1783
|
+
lines.push(` CRITICAL: ${summary.findingsBySeverity.critical}`);
|
|
1784
|
+
}
|
|
1785
|
+
if (summary.findingsBySeverity.high > 0) {
|
|
1786
|
+
lines.push(` HIGH: ${summary.findingsBySeverity.high}`);
|
|
1787
|
+
}
|
|
1788
|
+
if (summary.findingsBySeverity.medium > 0) {
|
|
1789
|
+
lines.push(` MEDIUM: ${summary.findingsBySeverity.medium}`);
|
|
1790
|
+
}
|
|
1791
|
+
if (summary.findingsBySeverity.low > 0) {
|
|
1792
|
+
lines.push(` LOW: ${summary.findingsBySeverity.low}`);
|
|
1793
|
+
}
|
|
1794
|
+
lines.push("");
|
|
1795
|
+
for (const result of summary.results) {
|
|
1796
|
+
if (result.findings.length === 0)
|
|
1797
|
+
continue;
|
|
1798
|
+
lines.push(`${result.capabilityId}:`);
|
|
1799
|
+
for (const finding of result.findings) {
|
|
1800
|
+
const location = finding.line ? `:${finding.line}${finding.column ? `:${finding.column}` : ""}` : "";
|
|
1801
|
+
const severity = finding.severity.toUpperCase().padEnd(8);
|
|
1802
|
+
lines.push(` [${severity}] ${finding.file}${location}`);
|
|
1803
|
+
lines.push(` ${finding.message}`);
|
|
1804
|
+
if (finding.details) {
|
|
1805
|
+
lines.push(` ${finding.details}`);
|
|
1806
|
+
}
|
|
1807
|
+
lines.push(` To allow: omnidev security allow ${result.capabilityId} ${finding.type}`);
|
|
1808
|
+
lines.push("");
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
return lines.join(`
|
|
1812
|
+
`);
|
|
1813
|
+
}
|
|
1814
|
+
async function runSecurityIssues(flags = {}) {
|
|
1815
|
+
try {
|
|
1816
|
+
if (!existsSync8("omni.toml")) {
|
|
1817
|
+
console.log("No config file found");
|
|
1818
|
+
console.log(" Run: omnidev init");
|
|
1819
|
+
process.exit(1);
|
|
1820
|
+
}
|
|
1821
|
+
console.log("Scanning capabilities for security issues...");
|
|
1822
|
+
console.log("");
|
|
1823
|
+
const registry = await buildCapabilityRegistry();
|
|
1824
|
+
const capabilities = registry.getAllCapabilities();
|
|
1825
|
+
if (capabilities.length === 0) {
|
|
1826
|
+
console.log("No capabilities found to scan.");
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1829
|
+
const capabilityInfos = capabilities.map((cap) => ({
|
|
1830
|
+
id: cap.id,
|
|
1831
|
+
path: cap.path
|
|
1832
|
+
}));
|
|
1833
|
+
const summary = await scanCapabilities(capabilityInfos);
|
|
1834
|
+
const filteredSummary = await filterAllowedFindings(summary, {
|
|
1835
|
+
includeLow: flags.all ?? false
|
|
1836
|
+
});
|
|
1837
|
+
if (flags.verbose) {
|
|
1838
|
+
console.log(formatScanResults(filteredSummary, true));
|
|
1839
|
+
} else {
|
|
1840
|
+
console.log(formatFindingsWithHints(filteredSummary));
|
|
1841
|
+
}
|
|
1842
|
+
const hiddenCount = summary.totalFindings - filteredSummary.totalFindings;
|
|
1843
|
+
if (hiddenCount > 0) {
|
|
1844
|
+
const parts = [];
|
|
1845
|
+
if (!flags.all && summary.findingsBySeverity.low > 0) {
|
|
1846
|
+
parts.push(`${summary.findingsBySeverity.low} low-severity`);
|
|
1847
|
+
}
|
|
1848
|
+
const allowedCount = hiddenCount - (flags.all ? 0 : summary.findingsBySeverity.low);
|
|
1849
|
+
if (allowedCount > 0) {
|
|
1850
|
+
parts.push(`${allowedCount} allowed`);
|
|
1851
|
+
}
|
|
1852
|
+
if (parts.length > 0) {
|
|
1853
|
+
console.log(`(${parts.join(", ")} finding(s) hidden, use --all to show)`);
|
|
1854
|
+
console.log("");
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
if (!filteredSummary.allPassed) {
|
|
1858
|
+
process.exit(1);
|
|
1859
|
+
}
|
|
1860
|
+
} catch (error) {
|
|
1861
|
+
console.error("Error scanning capabilities:", error);
|
|
1862
|
+
process.exit(1);
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
async function runSecurityAllow(_flags, capabilityId, findingType) {
|
|
1866
|
+
try {
|
|
1867
|
+
if (!isValidFindingType(findingType)) {
|
|
1868
|
+
console.error(`Invalid finding type: '${findingType}'`);
|
|
1869
|
+
console.log("");
|
|
1870
|
+
console.log("Valid types:");
|
|
1871
|
+
for (const type of VALID_FINDING_TYPES) {
|
|
1872
|
+
console.log(` - ${type}`);
|
|
1873
|
+
}
|
|
1874
|
+
process.exit(1);
|
|
1875
|
+
}
|
|
1876
|
+
const added = await addSecurityAllow(capabilityId, findingType);
|
|
1877
|
+
if (added) {
|
|
1878
|
+
console.log(`Allowed ${findingType} for capability: ${capabilityId}`);
|
|
1879
|
+
} else {
|
|
1880
|
+
console.log(`Already allowed: ${findingType} for ${capabilityId}`);
|
|
1881
|
+
}
|
|
1882
|
+
} catch (error) {
|
|
1883
|
+
console.error("Error adding security allow:", error);
|
|
1884
|
+
process.exit(1);
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
async function runSecurityDeny(_flags, capabilityId, findingType) {
|
|
1888
|
+
try {
|
|
1889
|
+
if (!isValidFindingType(findingType)) {
|
|
1890
|
+
console.error(`Invalid finding type: '${findingType}'`);
|
|
1891
|
+
console.log("");
|
|
1892
|
+
console.log("Valid types:");
|
|
1893
|
+
for (const type of VALID_FINDING_TYPES) {
|
|
1894
|
+
console.log(` - ${type}`);
|
|
1895
|
+
}
|
|
1896
|
+
process.exit(1);
|
|
1897
|
+
}
|
|
1898
|
+
const removed = await removeSecurityAllow(capabilityId, findingType);
|
|
1899
|
+
if (removed) {
|
|
1900
|
+
console.log(`Removed allow for ${findingType} on capability: ${capabilityId}`);
|
|
1901
|
+
} else {
|
|
1902
|
+
console.log(`No allow found for: ${findingType} on ${capabilityId}`);
|
|
1903
|
+
}
|
|
1904
|
+
} catch (error) {
|
|
1905
|
+
console.error("Error removing security allow:", error);
|
|
1906
|
+
process.exit(1);
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
async function runSecurityListAllows() {
|
|
1910
|
+
try {
|
|
1911
|
+
const allows = await getAllSecurityAllows();
|
|
1912
|
+
if (allows.length === 0) {
|
|
1913
|
+
console.log("No security allows configured.");
|
|
1914
|
+
console.log("");
|
|
1915
|
+
console.log("Use 'omnidev security allow <cap-id> <type>' to add allows.");
|
|
1916
|
+
return;
|
|
1917
|
+
}
|
|
1918
|
+
console.log("Security Allows:");
|
|
1919
|
+
console.log("");
|
|
1920
|
+
const byCapability = {};
|
|
1921
|
+
for (const allow of allows) {
|
|
1922
|
+
const existing = byCapability[allow.capabilityId];
|
|
1923
|
+
if (existing) {
|
|
1924
|
+
existing.push(allow.findingType);
|
|
1925
|
+
} else {
|
|
1926
|
+
byCapability[allow.capabilityId] = [allow.findingType];
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
for (const [capId, types] of Object.entries(byCapability)) {
|
|
1930
|
+
console.log(` ${capId}:`);
|
|
1931
|
+
for (const type of types) {
|
|
1932
|
+
console.log(` - ${type}`);
|
|
1933
|
+
}
|
|
1934
|
+
console.log("");
|
|
1935
|
+
}
|
|
1936
|
+
} catch (error) {
|
|
1937
|
+
console.error("Error listing security allows:", error);
|
|
1938
|
+
process.exit(1);
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
var issuesCommand = buildCommand7({
|
|
1942
|
+
docs: {
|
|
1943
|
+
brief: "Scan capabilities for security issues",
|
|
1944
|
+
fullDescription: `Scan all enabled capabilities for security issues.
|
|
1945
|
+
|
|
1946
|
+
Security checks include:
|
|
1947
|
+
- Suspicious Unicode characters (bidi overrides, zero-width, control chars)
|
|
1948
|
+
- Symlinks that escape capability directories
|
|
1949
|
+
- Suspicious script patterns (curl|sh, eval, rm -rf /, etc.)
|
|
1950
|
+
- Binary files in content directories
|
|
1951
|
+
|
|
1952
|
+
By default, low-severity findings (like binary files) are hidden.
|
|
1953
|
+
Use --all to show all findings including low-severity ones.
|
|
1954
|
+
|
|
1955
|
+
Findings can be allowed using 'omnidev security allow <cap-id> <type>'.
|
|
1956
|
+
|
|
1957
|
+
Examples:
|
|
1958
|
+
omnidev security issues
|
|
1959
|
+
omnidev security issues --all
|
|
1960
|
+
omnidev security issues --verbose`
|
|
1961
|
+
},
|
|
1962
|
+
parameters: {
|
|
1963
|
+
flags: {
|
|
1964
|
+
verbose: {
|
|
1965
|
+
kind: "boolean",
|
|
1966
|
+
brief: "Show verbose output with raw format",
|
|
1967
|
+
optional: true
|
|
1968
|
+
},
|
|
1969
|
+
all: {
|
|
1970
|
+
kind: "boolean",
|
|
1971
|
+
brief: "Show all findings including low-severity",
|
|
1972
|
+
optional: true
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
},
|
|
1976
|
+
async func(flags) {
|
|
1977
|
+
await runSecurityIssues(flags);
|
|
1978
|
+
}
|
|
1979
|
+
});
|
|
1980
|
+
var allowCommand = buildCommand7({
|
|
1981
|
+
docs: {
|
|
1982
|
+
brief: "Allow a security finding type for a capability",
|
|
1983
|
+
fullDescription: `Allow (ignore) a specific security finding type for a capability.
|
|
1984
|
+
|
|
1985
|
+
This stores the allow in .omni/security.json. Allowed findings are hidden
|
|
1986
|
+
from 'omnidev security issues' output.
|
|
1987
|
+
|
|
1988
|
+
Finding types:
|
|
1989
|
+
unicode_bidi - Bidirectional text override characters
|
|
1990
|
+
unicode_zero_width - Zero-width characters
|
|
1991
|
+
unicode_control - Suspicious control characters
|
|
1992
|
+
symlink_escape - Symlinks escaping capability directory
|
|
1993
|
+
symlink_absolute - Symlinks with absolute paths
|
|
1994
|
+
suspicious_script - Suspicious script patterns
|
|
1995
|
+
binary_file - Binary files in content directories
|
|
1996
|
+
|
|
1997
|
+
Examples:
|
|
1998
|
+
omnidev security allow my-capability unicode_bidi
|
|
1999
|
+
omnidev security allow my-capability suspicious_script`
|
|
2000
|
+
},
|
|
2001
|
+
parameters: {
|
|
2002
|
+
flags: {},
|
|
2003
|
+
positional: {
|
|
2004
|
+
kind: "tuple",
|
|
2005
|
+
parameters: [
|
|
2006
|
+
{
|
|
2007
|
+
brief: "Capability ID",
|
|
2008
|
+
parse: String
|
|
2009
|
+
},
|
|
2010
|
+
{
|
|
2011
|
+
brief: "Finding type to allow",
|
|
2012
|
+
parse: String
|
|
2013
|
+
}
|
|
2014
|
+
]
|
|
2015
|
+
}
|
|
2016
|
+
},
|
|
2017
|
+
func: runSecurityAllow
|
|
2018
|
+
});
|
|
2019
|
+
var denyCommand = buildCommand7({
|
|
2020
|
+
docs: {
|
|
2021
|
+
brief: "Remove a security allow",
|
|
2022
|
+
fullDescription: `Remove a previously allowed security finding type.
|
|
2023
|
+
|
|
2024
|
+
This removes the allow from .omni/security.json. The finding will
|
|
2025
|
+
appear again in 'omnidev security issues' output.
|
|
2026
|
+
|
|
2027
|
+
Examples:
|
|
2028
|
+
omnidev security deny my-capability unicode_bidi
|
|
2029
|
+
omnidev security deny my-capability suspicious_script`
|
|
2030
|
+
},
|
|
2031
|
+
parameters: {
|
|
2032
|
+
flags: {},
|
|
2033
|
+
positional: {
|
|
2034
|
+
kind: "tuple",
|
|
2035
|
+
parameters: [
|
|
2036
|
+
{
|
|
2037
|
+
brief: "Capability ID",
|
|
2038
|
+
parse: String
|
|
2039
|
+
},
|
|
2040
|
+
{
|
|
2041
|
+
brief: "Finding type to remove allow for",
|
|
2042
|
+
parse: String
|
|
2043
|
+
}
|
|
2044
|
+
]
|
|
2045
|
+
}
|
|
2046
|
+
},
|
|
2047
|
+
func: runSecurityDeny
|
|
2048
|
+
});
|
|
2049
|
+
var listAllowsCommand = buildCommand7({
|
|
2050
|
+
docs: {
|
|
2051
|
+
brief: "List all security allows"
|
|
2052
|
+
},
|
|
2053
|
+
parameters: {},
|
|
2054
|
+
async func() {
|
|
2055
|
+
await runSecurityListAllows();
|
|
2056
|
+
}
|
|
2057
|
+
});
|
|
2058
|
+
var securityRoutes = buildRouteMap5({
|
|
2059
|
+
routes: {
|
|
2060
|
+
issues: issuesCommand,
|
|
2061
|
+
allow: allowCommand,
|
|
2062
|
+
deny: denyCommand,
|
|
2063
|
+
"list-allows": listAllowsCommand
|
|
2064
|
+
},
|
|
2065
|
+
docs: {
|
|
2066
|
+
brief: "Security scanning and allows"
|
|
2067
|
+
}
|
|
2068
|
+
});
|
|
2069
|
+
|
|
2070
|
+
// src/commands/sync.ts
|
|
2071
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
1554
2072
|
import {
|
|
1555
2073
|
getActiveProfile as getActiveProfile3,
|
|
1556
2074
|
loadConfig as loadConfig3,
|
|
1557
2075
|
syncAgentConfiguration as syncAgentConfiguration6,
|
|
1558
2076
|
writeEnabledProviders as writeEnabledProviders2
|
|
1559
2077
|
} from "@omnidev-ai/core";
|
|
1560
|
-
import { buildCommand as
|
|
2078
|
+
import { buildCommand as buildCommand8 } from "@stricli/core";
|
|
1561
2079
|
var PROVIDERS_STATE_PATH = ".omni/state/providers.json";
|
|
1562
|
-
var syncCommand =
|
|
2080
|
+
var syncCommand = buildCommand8({
|
|
1563
2081
|
docs: {
|
|
1564
2082
|
brief: "Manually sync all capabilities, roles, and instructions"
|
|
1565
2083
|
},
|
|
@@ -1569,13 +2087,11 @@ var syncCommand = buildCommand7({
|
|
|
1569
2087
|
}
|
|
1570
2088
|
});
|
|
1571
2089
|
async function runSync() {
|
|
1572
|
-
console.log("Syncing OmniDev configuration...");
|
|
1573
|
-
console.log("");
|
|
1574
2090
|
try {
|
|
1575
2091
|
const config = await loadConfig3();
|
|
1576
2092
|
const activeProfile = await getActiveProfile3() ?? "default";
|
|
1577
2093
|
let adapters = await getEnabledAdapters();
|
|
1578
|
-
if (!
|
|
2094
|
+
if (!existsSync9(PROVIDERS_STATE_PATH) || adapters.length === 0) {
|
|
1579
2095
|
console.log("No providers configured yet. Select your provider(s):");
|
|
1580
2096
|
const providerIds = await promptForProviders();
|
|
1581
2097
|
await writeEnabledProviders2(providerIds);
|
|
@@ -1586,21 +2102,12 @@ async function runSync() {
|
|
|
1586
2102
|
adapters = await initializeAdaptersForProviders(providerIds, ctx);
|
|
1587
2103
|
console.log("");
|
|
1588
2104
|
}
|
|
1589
|
-
const
|
|
2105
|
+
const providerNames = adapters.map((a) => a.displayName).join(", ") || "none";
|
|
2106
|
+
console.log(`Profile: ${activeProfile} | Providers: ${providerNames}`);
|
|
1590
2107
|
console.log("");
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
console.log(
|
|
1594
|
-
console.log(`Capabilities: ${result.capabilities.join(", ") || "none"}`);
|
|
1595
|
-
console.log(`Providers: ${adapters.map((a) => a.displayName).join(", ") || "none"}`);
|
|
1596
|
-
console.log("");
|
|
1597
|
-
console.log("Synced components:");
|
|
1598
|
-
console.log(" • Capability registry");
|
|
1599
|
-
console.log(" • Capability sync hooks");
|
|
1600
|
-
console.log(" • .omni/.gitignore");
|
|
1601
|
-
if (adapters.length > 0) {
|
|
1602
|
-
console.log(" • Provider-specific files (instructions embedded)");
|
|
1603
|
-
}
|
|
2108
|
+
const result = await syncAgentConfiguration6({ silent: false, adapters });
|
|
2109
|
+
const capList = result.capabilities.join(", ");
|
|
2110
|
+
console.log(`✓ Synced - ${capList}`);
|
|
1604
2111
|
} catch (error) {
|
|
1605
2112
|
console.error("");
|
|
1606
2113
|
console.error("✗ Sync failed:");
|
|
@@ -1614,7 +2121,7 @@ import { debug } from "@omnidev-ai/core";
|
|
|
1614
2121
|
var require2 = createRequire2(import.meta.url);
|
|
1615
2122
|
function readCliVersion() {
|
|
1616
2123
|
try {
|
|
1617
|
-
const pkg = require2("
|
|
2124
|
+
const pkg = require2("../package.json");
|
|
1618
2125
|
if (typeof pkg?.version === "string") {
|
|
1619
2126
|
return pkg.version;
|
|
1620
2127
|
}
|
|
@@ -1629,11 +2136,15 @@ async function buildDynamicApp() {
|
|
|
1629
2136
|
add: addRoutes,
|
|
1630
2137
|
capability: capabilityRoutes,
|
|
1631
2138
|
profile: profileRoutes,
|
|
1632
|
-
provider: providerRoutes
|
|
2139
|
+
provider: providerRoutes,
|
|
2140
|
+
security: securityRoutes
|
|
1633
2141
|
};
|
|
1634
2142
|
debug("Core routes registered", Object.keys(routes));
|
|
1635
|
-
|
|
2143
|
+
const configPath = join9(process.cwd(), "omni.toml");
|
|
2144
|
+
debug("Checking for config", { configPath, exists: existsSync10(configPath), cwd: process.cwd() });
|
|
2145
|
+
if (existsSync10(configPath)) {
|
|
1636
2146
|
try {
|
|
2147
|
+
debug("Loading capability commands...");
|
|
1637
2148
|
const capabilityCommands = await loadCapabilityCommands();
|
|
1638
2149
|
debug("Capability commands loaded", {
|
|
1639
2150
|
commands: Object.keys(capabilityCommands),
|
|
@@ -1653,7 +2164,7 @@ async function buildDynamicApp() {
|
|
|
1653
2164
|
}
|
|
1654
2165
|
}
|
|
1655
2166
|
debug("Final routes", Object.keys(routes));
|
|
1656
|
-
const app = buildApplication(
|
|
2167
|
+
const app = buildApplication(buildRouteMap6({
|
|
1657
2168
|
routes,
|
|
1658
2169
|
docs: {
|
|
1659
2170
|
brief: "OmniDev commands"
|
|
@@ -1668,10 +2179,14 @@ async function buildDynamicApp() {
|
|
|
1668
2179
|
return app;
|
|
1669
2180
|
}
|
|
1670
2181
|
async function loadCapabilityCommands() {
|
|
1671
|
-
const { buildCapabilityRegistry, installCapabilityDependencies } = await import("@omnidev-ai/core");
|
|
2182
|
+
const { buildCapabilityRegistry: buildCapabilityRegistry2, installCapabilityDependencies } = await import("@omnidev-ai/core");
|
|
1672
2183
|
await installCapabilityDependencies(true);
|
|
1673
|
-
const registry = await
|
|
2184
|
+
const registry = await buildCapabilityRegistry2();
|
|
1674
2185
|
const capabilities = registry.getAllCapabilities();
|
|
2186
|
+
debug("Registry built", {
|
|
2187
|
+
capabilityCount: capabilities.length,
|
|
2188
|
+
capabilities: capabilities.map((c) => ({ id: c.id, path: c.path }))
|
|
2189
|
+
});
|
|
1675
2190
|
const commands = {};
|
|
1676
2191
|
for (const capability of capabilities) {
|
|
1677
2192
|
try {
|
|
@@ -1702,20 +2217,40 @@ async function loadCapabilityCommands() {
|
|
|
1702
2217
|
}
|
|
1703
2218
|
async function loadCapabilityExport(capability) {
|
|
1704
2219
|
const capabilityPath = join9(process.cwd(), capability.path);
|
|
1705
|
-
const
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
2220
|
+
const builtIndexPath = join9(capabilityPath, "dist", "index.js");
|
|
2221
|
+
const jsIndexPath = join9(capabilityPath, "index.js");
|
|
2222
|
+
const tsIndexPath = join9(capabilityPath, "index.ts");
|
|
2223
|
+
debug(`Checking entry points for '${capability.id}'`, {
|
|
2224
|
+
capabilityPath,
|
|
2225
|
+
builtIndexPath,
|
|
2226
|
+
builtExists: existsSync10(builtIndexPath),
|
|
2227
|
+
jsIndexPath,
|
|
2228
|
+
jsExists: existsSync10(jsIndexPath),
|
|
2229
|
+
tsIndexPath,
|
|
2230
|
+
tsExists: existsSync10(tsIndexPath)
|
|
2231
|
+
});
|
|
2232
|
+
let indexPath = null;
|
|
2233
|
+
if (existsSync10(builtIndexPath)) {
|
|
2234
|
+
indexPath = builtIndexPath;
|
|
2235
|
+
} else if (existsSync10(jsIndexPath)) {
|
|
2236
|
+
indexPath = jsIndexPath;
|
|
2237
|
+
} else if (existsSync10(tsIndexPath)) {
|
|
2238
|
+
indexPath = tsIndexPath;
|
|
1716
2239
|
}
|
|
2240
|
+
if (!indexPath) {
|
|
2241
|
+
debug(`No entry point found for '${capability.id}'`);
|
|
2242
|
+
return null;
|
|
2243
|
+
}
|
|
2244
|
+
debug(`Using entry point for '${capability.id}'`, { indexPath });
|
|
1717
2245
|
const module = await import(indexPath);
|
|
2246
|
+
debug(`Module loaded for '${capability.id}'`, {
|
|
2247
|
+
hasDefault: !!module.default,
|
|
2248
|
+
moduleKeys: Object.keys(module),
|
|
2249
|
+
defaultType: typeof module.default,
|
|
2250
|
+
defaultKeys: module.default ? Object.keys(module.default) : []
|
|
2251
|
+
});
|
|
1718
2252
|
if (!module.default) {
|
|
2253
|
+
debug(`No default export for '${capability.id}'`);
|
|
1719
2254
|
return null;
|
|
1720
2255
|
}
|
|
1721
2256
|
const capExport = module.default;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omnidev-ai/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -28,11 +28,11 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@inquirer/prompts": "^8.1.0",
|
|
31
|
-
"@omnidev-ai/core": "0.
|
|
31
|
+
"@omnidev-ai/core": "0.12.0",
|
|
32
32
|
"@stricli/core": "^1.2.5"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@omnidev-ai/adapters": "0.
|
|
35
|
+
"@omnidev-ai/adapters": "0.1.1",
|
|
36
36
|
"bunup": "^0.16.20"
|
|
37
37
|
}
|
|
38
38
|
}
|