solidity-argus 0.1.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 (131) hide show
  1. package/AGENTS.md +37 -0
  2. package/LICENSE +21 -0
  3. package/README.md +249 -0
  4. package/package.json +43 -0
  5. package/skills/INVENTORY.md +79 -0
  6. package/skills/README.md +56 -0
  7. package/skills/checklists/cyfrin-best-practices-runtime/SKILL.md +424 -0
  8. package/skills/checklists/cyfrin-best-practices-upgrades/SKILL.md +157 -0
  9. package/skills/checklists/cyfrin-defi-core/SKILL.md +373 -0
  10. package/skills/checklists/cyfrin-defi-integrations/SKILL.md +412 -0
  11. package/skills/checklists/cyfrin-gas/SKILL.md +55 -0
  12. package/skills/checklists/general-audit/SKILL.md +433 -0
  13. package/skills/methodology/audit-workflow/SKILL.md +129 -0
  14. package/skills/methodology/report-template/SKILL.md +190 -0
  15. package/skills/methodology/severity-classification/SKILL.md +179 -0
  16. package/skills/protocol-patterns/amm-dex/SKILL.md +229 -0
  17. package/skills/protocol-patterns/bridges-cross-chain/SKILL.md +317 -0
  18. package/skills/protocol-patterns/dao-governance/SKILL.md +281 -0
  19. package/skills/protocol-patterns/lending-borrowing/SKILL.md +221 -0
  20. package/skills/protocol-patterns/staking-vesting/SKILL.md +247 -0
  21. package/skills/references/exploit-reference/SKILL.md +259 -0
  22. package/skills/references/smartbugs-examples/SKILL.md +296 -0
  23. package/skills/vulnerability-patterns/access-control/SKILL.md +298 -0
  24. package/skills/vulnerability-patterns/arbitrary-storage-location/SKILL.md +59 -0
  25. package/skills/vulnerability-patterns/assert-violation/SKILL.md +59 -0
  26. package/skills/vulnerability-patterns/asserting-contract-from-code-size/SKILL.md +61 -0
  27. package/skills/vulnerability-patterns/authorization-txorigin/SKILL.md +55 -0
  28. package/skills/vulnerability-patterns/default-visibility/SKILL.md +62 -0
  29. package/skills/vulnerability-patterns/delegatecall-untrusted-callee/SKILL.md +60 -0
  30. package/skills/vulnerability-patterns/dos-gas-limit/SKILL.md +59 -0
  31. package/skills/vulnerability-patterns/dos-revert/SKILL.md +72 -0
  32. package/skills/vulnerability-patterns/flash-loan-attacks/SKILL.md +249 -0
  33. package/skills/vulnerability-patterns/floating-pragma/SKILL.md +51 -0
  34. package/skills/vulnerability-patterns/hash-collision/SKILL.md +52 -0
  35. package/skills/vulnerability-patterns/inadherence-to-standards/SKILL.md +61 -0
  36. package/skills/vulnerability-patterns/incorrect-constructor/SKILL.md +60 -0
  37. package/skills/vulnerability-patterns/incorrect-inheritance-order/SKILL.md +59 -0
  38. package/skills/vulnerability-patterns/insufficient-gas-griefing/SKILL.md +61 -0
  39. package/skills/vulnerability-patterns/lack-of-precision/SKILL.md +61 -0
  40. package/skills/vulnerability-patterns/logic-errors/SKILL.md +333 -0
  41. package/skills/vulnerability-patterns/missing-protection-signature-replay/SKILL.md +60 -0
  42. package/skills/vulnerability-patterns/msgvalue-loop/SKILL.md +66 -0
  43. package/skills/vulnerability-patterns/off-by-one/SKILL.md +67 -0
  44. package/skills/vulnerability-patterns/oracle-manipulation/SKILL.md +252 -0
  45. package/skills/vulnerability-patterns/outdated-compiler-version/SKILL.md +65 -0
  46. package/skills/vulnerability-patterns/overflow-underflow/SKILL.md +61 -0
  47. package/skills/vulnerability-patterns/reentrancy/SKILL.md +266 -0
  48. package/skills/vulnerability-patterns/shadowing-state-variables/SKILL.md +72 -0
  49. package/skills/vulnerability-patterns/signature-malleability/SKILL.md +59 -0
  50. package/skills/vulnerability-patterns/unbounded-return-data/SKILL.md +63 -0
  51. package/skills/vulnerability-patterns/unchecked-return-values/SKILL.md +52 -0
  52. package/skills/vulnerability-patterns/unencrypted-private-data-on-chain/SKILL.md +65 -0
  53. package/skills/vulnerability-patterns/unexpected-ecrecover-null-address/SKILL.md +61 -0
  54. package/skills/vulnerability-patterns/uninitialized-storage-pointer/SKILL.md +63 -0
  55. package/skills/vulnerability-patterns/unsafe-low-level-call/SKILL.md +56 -0
  56. package/skills/vulnerability-patterns/unsecure-signatures/SKILL.md +80 -0
  57. package/skills/vulnerability-patterns/unsupported-opcodes/SKILL.md +69 -0
  58. package/skills/vulnerability-patterns/unused-variables/SKILL.md +70 -0
  59. package/skills/vulnerability-patterns/use-of-deprecated-functions/SKILL.md +81 -0
  60. package/skills/vulnerability-patterns/weak-sources-randomness/SKILL.md +77 -0
  61. package/skills/vulnerability-patterns/weird-tokens/SKILL.md +294 -0
  62. package/src/agents/argus-prompt.ts +407 -0
  63. package/src/agents/pythia-prompt.ts +134 -0
  64. package/src/agents/scribe-prompt.ts +87 -0
  65. package/src/agents/sentinel-prompt.ts +133 -0
  66. package/src/cli/cli-program.ts +67 -0
  67. package/src/cli/commands/doctor.ts +83 -0
  68. package/src/cli/commands/init.ts +46 -0
  69. package/src/cli/commands/install.ts +55 -0
  70. package/src/cli/index.ts +13 -0
  71. package/src/cli/tui-prompts.ts +75 -0
  72. package/src/cli/types.ts +9 -0
  73. package/src/config/index.ts +3 -0
  74. package/src/config/loader.ts +36 -0
  75. package/src/config/schema.ts +82 -0
  76. package/src/config/types.ts +4 -0
  77. package/src/constants/defaults.ts +6 -0
  78. package/src/create-hooks.ts +84 -0
  79. package/src/create-managers.ts +26 -0
  80. package/src/create-tools.ts +30 -0
  81. package/src/features/audit-enforcer/audit-enforcer.ts +34 -0
  82. package/src/features/audit-enforcer/index.ts +1 -0
  83. package/src/features/background-agent/background-manager.ts +200 -0
  84. package/src/features/background-agent/index.ts +1 -0
  85. package/src/features/context-monitor/context-monitor.ts +48 -0
  86. package/src/features/context-monitor/index.ts +4 -0
  87. package/src/features/context-monitor/tool-output-truncator.ts +17 -0
  88. package/src/features/error-recovery/index.ts +2 -0
  89. package/src/features/error-recovery/session-recovery.ts +27 -0
  90. package/src/features/error-recovery/tool-error-recovery.ts +35 -0
  91. package/src/features/index.ts +5 -0
  92. package/src/features/persistent-state/audit-state-manager.ts +121 -0
  93. package/src/features/persistent-state/index.ts +1 -0
  94. package/src/hooks/compaction-hook.ts +50 -0
  95. package/src/hooks/config-handler.ts +116 -0
  96. package/src/hooks/event-hook-v2.ts +93 -0
  97. package/src/hooks/event-hook.ts +74 -0
  98. package/src/hooks/hook-system.ts +9 -0
  99. package/src/hooks/index.ts +5 -0
  100. package/src/hooks/knowledge-sync-hook.ts +57 -0
  101. package/src/hooks/safe-create-hook.ts +15 -0
  102. package/src/hooks/system-prompt-hook.ts +126 -0
  103. package/src/hooks/tool-tracking-hook.ts +234 -0
  104. package/src/hooks/types.ts +16 -0
  105. package/src/index.ts +36 -0
  106. package/src/knowledge/scvd-client.ts +242 -0
  107. package/src/knowledge/scvd-index.ts +183 -0
  108. package/src/knowledge/scvd-sync.ts +85 -0
  109. package/src/managers/index.ts +1 -0
  110. package/src/managers/types.ts +85 -0
  111. package/src/plugin-interface.ts +38 -0
  112. package/src/shared/binary-utils.ts +63 -0
  113. package/src/shared/deep-merge.ts +71 -0
  114. package/src/shared/file-utils.ts +56 -0
  115. package/src/shared/index.ts +5 -0
  116. package/src/shared/jsonc-parser.ts +39 -0
  117. package/src/shared/logger.ts +36 -0
  118. package/src/state/audit-state.ts +27 -0
  119. package/src/state/finding-store.ts +126 -0
  120. package/src/state/plugin-state.ts +14 -0
  121. package/src/state/types.ts +61 -0
  122. package/src/tools/contract-analyzer-tool.ts +184 -0
  123. package/src/tools/forge-fuzz-tool.ts +311 -0
  124. package/src/tools/forge-test-tool.ts +397 -0
  125. package/src/tools/pattern-checker-tool.ts +337 -0
  126. package/src/tools/report-generator-tool.ts +308 -0
  127. package/src/tools/slither-tool.ts +465 -0
  128. package/src/tools/solodit-search-tool.ts +131 -0
  129. package/src/tools/sync-knowledge-tool.ts +116 -0
  130. package/src/utils/project-detector.ts +133 -0
  131. package/src/utils/solidity-parser.ts +174 -0
@@ -0,0 +1,133 @@
1
+
2
+ export const SENTINEL_PROMPT = `You are **Sentinel**, the Tactical Guardian — a specialized subagent of Argus Panoptes. You are the "hands" of the audit, responsible for rigorous execution, static analysis, and dynamic verification. While Argus strategizes, you hunt.
3
+
4
+ ## IDENTITY & ROLE
5
+
6
+ You combine the precision of a static analyzer with the creativity of a white-hat hacker. You do not just run tools; you interpret their output, filter false positives, and prove vulnerabilities through code.
7
+
8
+ Your core responsibilities are:
9
+ 1. **Static Analysis**: Scanning codebases for known vulnerabilities and code quality issues.
10
+ 2. **Pattern Matching**: Identifying complex vulnerability patterns that standard tools miss.
11
+ 3. **Dynamic Testing**: Writing and executing tests to reproduce bugs (Proof of Concept).
12
+ 4. **Fuzzing**: Stress-testing logic with random inputs to find edge cases.
13
+
14
+ ## WORKFLOW
15
+
16
+ You operate in a loop of **Scan -> Analyze -> Verify**.
17
+
18
+ 1. **Broad Scan**:
19
+ - Start with \`argus_slither_analyze\` to get a high-level overview of potential issues.
20
+ - Use \`argus_check_patterns\` to scan for specific dangerous patterns (e.g., read-only reentrancy).
21
+
22
+ 2. **Deep Analysis**:
23
+ - For interesting contracts, use \`argus_analyze_contract\` to understand their structure, inheritance, and risk indicators.
24
+ - Manually review the code highlighted by Slither or pattern checks.
25
+
26
+ 3. **Targeted Verification**:
27
+ - If you suspect a bug, write a reproduction test case.
28
+ - Use \`argus_forge_test\` to run this test.
29
+ - If the logic is complex (e.g., math, state transitions), use \`argus_forge_fuzz\` to hammer it with inputs.
30
+
31
+ 4. **Reporting**:
32
+ - Format your findings strictly according to the Output Format section.
33
+ - Report back to Argus with confirmed findings.
34
+
35
+ ## TOOL USAGE GUIDE
36
+
37
+ You have access to a specific set of tools. Use them effectively.
38
+
39
+ ### 1. \`argus_slither_analyze\`
40
+ **Purpose**: Fast, broad static analysis.
41
+ **When to use**: At the start of an engagement or when analyzing a new file.
42
+ **Arguments**:
43
+ - \`target\` (string): Path to the directory (e.g., ".") or specific file.
44
+ - \`detectors\` (string[]): Optional list of specific detectors to run.
45
+ - \`exclude\` (string[]): Optional list of detectors to ignore.
46
+ **Interpretation**:
47
+ - Slither produces many false positives. **Verify every finding.**
48
+ - Look for "High" impact issues first, but don't ignore "Informational" ones—they often hint at sloppy coding practices.
49
+
50
+ ### 2. \`argus_analyze_contract\`
51
+ **Purpose**: Structural profiling of a contract.
52
+ **When to use**: Before writing tests or when you need to understand inheritance and state variables.
53
+ **Arguments**:
54
+ - \`file_path\` (string): The absolute or relative path to the .sol file.
55
+ **Interpretation**:
56
+ - Use the output to map out the "Attack Surface".
57
+ - Pay attention to \`riskIndicators\` (e.g., \`uses-delegatecall\`, \`uses-assembly\`).
58
+
59
+ ### 3. \`argus_check_patterns\`
60
+ **Purpose**: Regex-based pattern matching for specific vulnerabilities.
61
+ **When to use**: To find issues that Slither might miss, or to check for specific attack vectors (e.g., "reentrancy", "access-control").
62
+ **Arguments**:
63
+ - \`target\` (string): Path to file or directory.
64
+ - \`patterns\` (string[]): Optional list of pattern categories.
65
+ **Interpretation**:
66
+ - These are raw matches. Context is everything. A match for \`tx.origin\` is only a bug if used for authorization.
67
+
68
+ ### 4. \`argus_forge_test\`
69
+ **Purpose**: Run Foundry tests to confirm vulnerabilities.
70
+ **When to use**: To prove a bug exists (PoC) or to verify a fix.
71
+ **Arguments**:
72
+ - \`target\` (string): Path to the test file or directory (default ".").
73
+ - \`match_test\` (string): Name of the specific test function to run (e.g., "testExploit"). **Crucial for speed.**
74
+ - \`match_contract\` (string): Name of the contract to test.
75
+ - \`verbosity\` (number): 1-5. Use 3 or 4 to see traces.
76
+ **Interpretation**:
77
+ - If the test passes (and it was meant to fail/exploit), the bug might not exist, or the test is wrong.
78
+ - Analyze the stack trace in the output to understand *why* it reverted.
79
+
80
+ ### 5. \`argus_forge_fuzz\`
81
+ **Purpose**: Property-based testing (fuzzing).
82
+ **When to use**: For arithmetic, complex state transitions, or invariant checking.
83
+ **Arguments**:
84
+ - \`target\` (string): Path to test file.
85
+ - \`match_test\` (string): The fuzz test function (must have arguments).
86
+ - \`runs\` (number): Number of runs (default 256). Increase to 1000+ for deep bugs.
87
+ **Interpretation**:
88
+ - Look at the \`counterexamples\`. They tell you exactly what inputs broke the code.
89
+
90
+ ## OUTPUT FORMAT
91
+
92
+ Return your findings to Argus in this structured Markdown format. Do not deviate.
93
+
94
+ \`\`\`markdown
95
+ ## Finding: [SEVERITY] {Title}
96
+ **Severity**: {Critical|High|Medium|Low|Informational}
97
+ **Location**: {File}:{StartLine}-{EndLine}
98
+ **Description**:
99
+ {Clear explanation of the vulnerability. How does it happen? Why is it bad?}
100
+
101
+ **Impact**:
102
+ {Specific impact: Loss of funds, frozen funds, broken access control, etc.}
103
+
104
+ **Proof of Concept**:
105
+ {Describe the steps or provide the test code used to verify this.}
106
+
107
+ **Recommendation**:
108
+ {How to fix it. Be specific (e.g., "Add a reentrancy guard", "Use SafeMath").}
109
+ \`\`\`
110
+
111
+ ## ESCALATION & FALLBACK
112
+
113
+ 1. **Escalation**:
114
+ - If you find a **Critical** vulnerability (e.g., direct fund theft), stop and report it immediately.
115
+ - If you are unsure if a behavior is a bug or a feature, flag it as "Needs Review" and describe the ambiguity.
116
+
117
+ 2. **Fallback Procedures**:
118
+ - **Slither fails**: It happens. Fall back to \`argus_analyze_contract\` and read the code manually. Use \`argus_check_patterns\` to catch low-hanging fruit.
119
+ - **Forge fails**: If you cannot run tests (e.g., compilation errors in the project), rely on "Mental Execution". Trace the code logic step-by-step in your analysis. State clearly: "Verified via manual analysis (tests unavailable)."
120
+
121
+ ## EXECUTION MINDSET
122
+
123
+ - **Trust Code, Not Comments**: Comments often lie or are outdated. Read the implementation.
124
+ - **Think Adversarially**: How would *you* break this?
125
+ - **Verify Assumptions**: Does that modifier actually do what it says? Is that external call safe?
126
+ - **Be Precise**: A vague finding is useless. Point to the line, the variable, the specific interaction.
127
+
128
+ You are the Sentinel. The code cannot hide its secrets from you.
129
+ `;
130
+
131
+ export function getSentinelPrompt(): string {
132
+ return SENTINEL_PROMPT;
133
+ }
@@ -0,0 +1,67 @@
1
+ import type { CliCommand } from "./types";
2
+
3
+ const HELP_TEXT = `argus — Solidity Security Auditor for OpenCode
4
+
5
+ Commands:
6
+ doctor Check Slither/Foundry installation and config health
7
+ init Create solidity-argus config file
8
+ install Configure argus plugin in opencode config
9
+ `;
10
+
11
+ export class CliProgram {
12
+ private commands: Map<string, CliCommand> = new Map();
13
+
14
+ registerCommand(command: CliCommand): void {
15
+ this.commands.set(command.name, command);
16
+ }
17
+
18
+ async dispatch(args: string[]): Promise<number> {
19
+ const subcommand = args[0];
20
+
21
+ if (!subcommand || subcommand === "--help" || subcommand === "-h") {
22
+ console.log(HELP_TEXT);
23
+ return 0;
24
+ }
25
+
26
+ const command = this.commands.get(subcommand);
27
+ if (!command) {
28
+ console.error(`Error: Unknown command '${subcommand}'. Run 'argus' for help.`);
29
+ return 1;
30
+ }
31
+
32
+ return command.execute(args.slice(1));
33
+ }
34
+ }
35
+
36
+ export function createCliProgram(): CliProgram {
37
+ const program = new CliProgram();
38
+
39
+ program.registerCommand({
40
+ name: "doctor",
41
+ description: "Check Slither/Foundry installation and config health",
42
+ execute: async () => {
43
+ console.log("argus doctor: not yet implemented");
44
+ return 0;
45
+ },
46
+ });
47
+
48
+ program.registerCommand({
49
+ name: "init",
50
+ description: "Create solidity-argus config file",
51
+ execute: async () => {
52
+ console.log("argus init: not yet implemented");
53
+ return 0;
54
+ },
55
+ });
56
+
57
+ program.registerCommand({
58
+ name: "install",
59
+ description: "Configure argus plugin in opencode config",
60
+ execute: async () => {
61
+ console.log("argus install: not yet implemented");
62
+ return 0;
63
+ },
64
+ });
65
+
66
+ return program;
67
+ }
@@ -0,0 +1,83 @@
1
+ import { execSync } from "node:child_process"
2
+ import { existsSync } from "node:fs"
3
+ import { join } from "node:path"
4
+ import type { CliCommand } from "../types"
5
+ import { loadArgusConfig } from "../../config/loader"
6
+
7
+ const GREEN = "\x1b[32m"
8
+ const RED = "\x1b[31m"
9
+ const YELLOW = "\x1b[33m"
10
+ const RESET = "\x1b[0m"
11
+
12
+ function checkBinary(name: string): { found: boolean; version: string | null } {
13
+ try {
14
+ const version = execSync(`${name} --version`, { timeout: 5000 })
15
+ .toString()
16
+ .trim()
17
+ .split("\n")[0] ?? null
18
+ return { found: true, version }
19
+ } catch {
20
+ return { found: false, version: null }
21
+ }
22
+ }
23
+
24
+ function checkSolidityProject(dir: string): string | null {
25
+ if (existsSync(join(dir, "foundry.toml"))) return "foundry"
26
+ if (existsSync(join(dir, "hardhat.config.js"))) return "hardhat"
27
+ if (existsSync(join(dir, "hardhat.config.ts"))) return "hardhat"
28
+ return null
29
+ }
30
+
31
+ export const doctorCommand: CliCommand = {
32
+ name: "doctor",
33
+ description: "Check tool dependencies and configuration",
34
+ async execute(args: string[]): Promise<number> {
35
+ const cwd = process.cwd()
36
+ let hasFailure = false
37
+
38
+ console.log("Argus Doctor\n")
39
+
40
+ const slither = checkBinary("slither")
41
+ if (slither.found) {
42
+ console.log(`${GREEN}✓${RESET} Slither: installed (${slither.version})`)
43
+ } else {
44
+ console.log(`${RED}✗${RESET} Slither: not found — pip install slither-analyzer`)
45
+ hasFailure = true
46
+ }
47
+
48
+ const forge = checkBinary("forge")
49
+ if (forge.found) {
50
+ console.log(`${GREEN}✓${RESET} Forge: installed (${forge.version})`)
51
+ } else {
52
+ console.log(`${RED}✗${RESET} Forge: not found — curl -L https://foundry.paradigm.xyz | bash`)
53
+ hasFailure = true
54
+ }
55
+
56
+ const projectType = checkSolidityProject(cwd)
57
+ if (projectType) {
58
+ console.log(`${GREEN}✓${RESET} Project: ${projectType} detected`)
59
+ } else {
60
+ console.log(`${YELLOW}⚠${RESET} Project: no Solidity project detected`)
61
+ }
62
+
63
+ try {
64
+ const config = loadArgusConfig(cwd)
65
+ console.log(`${GREEN}✓${RESET} Config: valid`)
66
+ } catch {
67
+ console.log(`${YELLOW}⚠${RESET} Config: using defaults`)
68
+ }
69
+
70
+ try {
71
+ const response = await fetch("https://api.scvd.dev", { signal: AbortSignal.timeout(5000) })
72
+ if (response.ok) {
73
+ console.log(`${GREEN}✓${RESET} SCVD API: reachable`)
74
+ } else {
75
+ console.log(`${YELLOW}⚠${RESET} SCVD API: returned ${response.status}`)
76
+ }
77
+ } catch {
78
+ console.log(`${YELLOW}⚠${RESET} SCVD API: unreachable`)
79
+ }
80
+
81
+ return hasFailure ? 1 : 0
82
+ },
83
+ }
@@ -0,0 +1,46 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs"
2
+ import { join } from "node:path"
3
+ import type { CliCommand } from "../types"
4
+
5
+ const GREEN = "\x1b[32m"
6
+ const YELLOW = "\x1b[33m"
7
+ const RESET = "\x1b[0m"
8
+
9
+ const DEFAULT_CONFIG = {
10
+ agents: {},
11
+ tools: {},
12
+ knowledge: { scvd: { enabled: true }, autoSync: true },
13
+ reporting: { format: "markdown", severityThreshold: "low" },
14
+ solodit: { enabled: true },
15
+ }
16
+
17
+ export const initCommand: CliCommand = {
18
+ name: "init",
19
+ description: "Initialize Argus configuration for this project",
20
+ async execute(args: string[]): Promise<number> {
21
+ const cwd = process.cwd()
22
+ const configDir = join(cwd, ".opencode")
23
+ const configPath = join(configDir, "solidity-argus.json")
24
+
25
+ if (existsSync(configPath)) {
26
+ console.error(`${YELLOW}⚠${RESET} Config already exists: ${configPath}`)
27
+ console.error(" Remove it first if you want to reinitialize.")
28
+ return 1
29
+ }
30
+
31
+ mkdirSync(configDir, { recursive: true })
32
+ writeFileSync(configPath, `${JSON.stringify(DEFAULT_CONFIG, null, 2)}\n`)
33
+
34
+ const projectType = existsSync(join(cwd, "foundry.toml"))
35
+ ? "Foundry"
36
+ : existsSync(join(cwd, "hardhat.config.js")) || existsSync(join(cwd, "hardhat.config.ts"))
37
+ ? "Hardhat"
38
+ : "unknown"
39
+
40
+ console.log(`${GREEN}✓${RESET} Created ${configPath}`)
41
+ console.log(` Project type: ${projectType}`)
42
+ console.log(" Run 'argus doctor' to check dependencies.")
43
+
44
+ return 0
45
+ },
46
+ }
@@ -0,0 +1,55 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs"
2
+ import { join } from "node:path"
3
+ import { homedir } from "node:os"
4
+ import type { CliCommand } from "../types"
5
+
6
+ const GREEN = "\x1b[32m"
7
+ const YELLOW = "\x1b[33m"
8
+ const RESET = "\x1b[0m"
9
+
10
+ export function findOpencodeConfig(homeOverride?: string): string | null {
11
+ const cwd = process.cwd()
12
+ const localPath = join(cwd, "opencode.json")
13
+ if (existsSync(localPath)) return localPath
14
+
15
+ const home = homeOverride ?? homedir()
16
+ const globalPath = join(home, ".config", "opencode", "opencode.json")
17
+ if (existsSync(globalPath)) return globalPath
18
+
19
+ return null
20
+ }
21
+
22
+ export const installCommand: CliCommand = {
23
+ name: "install",
24
+ description: "Register solidity-argus in your OpenCode config",
25
+ async execute(args: string[]): Promise<number> {
26
+ const configPath = findOpencodeConfig()
27
+
28
+ if (!configPath) {
29
+ console.error(`${YELLOW}⚠${RESET} opencode.json not found`)
30
+ console.error(" Create one first, or run: opencode init")
31
+ return 1
32
+ }
33
+
34
+ try {
35
+ const content = readFileSync(configPath, "utf-8")
36
+ const config = JSON.parse(content)
37
+ const plugins: string[] = config.plugin ?? []
38
+
39
+ if (plugins.includes("solidity-argus")) {
40
+ console.log(`${GREEN}✓${RESET} solidity-argus already registered in ${configPath}`)
41
+ return 0
42
+ }
43
+
44
+ plugins.push("solidity-argus")
45
+ config.plugin = plugins
46
+ writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`)
47
+
48
+ console.log(`${GREEN}✓${RESET} Added solidity-argus to ${configPath}`)
49
+ return 0
50
+ } catch (error) {
51
+ console.error(`${YELLOW}⚠${RESET} Failed to update ${configPath}`)
52
+ return 1
53
+ }
54
+ },
55
+ }
@@ -0,0 +1,13 @@
1
+ import { createCliProgram } from "./cli-program";
2
+
3
+ async function main(): Promise<void> {
4
+ const program = createCliProgram();
5
+ const args = Bun.argv.slice(2);
6
+ const exitCode = await program.dispatch(args);
7
+ process.exit(exitCode);
8
+ }
9
+
10
+ main().catch((error) => {
11
+ console.error("Fatal error:", error);
12
+ process.exit(1);
13
+ });
@@ -0,0 +1,75 @@
1
+ const NON_INTERACTIVE =
2
+ !process.stdin.isTTY || process.env.CI === "true" || process.env.ARGUS_NON_INTERACTIVE === "true"
3
+
4
+ export async function confirm(message: string, defaultValue = true): Promise<boolean> {
5
+ if (NON_INTERACTIVE) return defaultValue
6
+
7
+ const hint = defaultValue ? "[Y/n]" : "[y/N]"
8
+ process.stdout.write(`${message} ${hint} `)
9
+
10
+ return new Promise((resolve) => {
11
+ const timeout = setTimeout(() => {
12
+ process.stdin.removeAllListeners("data")
13
+ resolve(defaultValue)
14
+ }, 30_000)
15
+
16
+ process.stdin.once("data", (data) => {
17
+ clearTimeout(timeout)
18
+ const input = data.toString().trim().toLowerCase()
19
+ if (input === "") resolve(defaultValue)
20
+ else resolve(input === "y" || input === "yes")
21
+ })
22
+ })
23
+ }
24
+
25
+ export async function select(
26
+ message: string,
27
+ options: string[],
28
+ defaultIndex = 0,
29
+ ): Promise<string> {
30
+ if (NON_INTERACTIVE) return options[defaultIndex] ?? options[0] ?? ""
31
+
32
+ console.log(message)
33
+ for (let i = 0; i < options.length; i++) {
34
+ const marker = i === defaultIndex ? ">" : " "
35
+ console.log(` ${marker} ${i + 1}. ${options[i]}`)
36
+ }
37
+
38
+ return new Promise((resolve) => {
39
+ const timeout = setTimeout(() => {
40
+ process.stdin.removeAllListeners("data")
41
+ resolve(options[defaultIndex] ?? options[0] ?? "")
42
+ }, 30_000)
43
+
44
+ process.stdin.once("data", (data) => {
45
+ clearTimeout(timeout)
46
+ const input = data.toString().trim()
47
+ const num = parseInt(input, 10)
48
+ if (num >= 1 && num <= options.length) {
49
+ resolve(options[num - 1] ?? options[0] ?? "")
50
+ } else {
51
+ resolve(options[defaultIndex] ?? options[0] ?? "")
52
+ }
53
+ })
54
+ })
55
+ }
56
+
57
+ export async function text(message: string, defaultValue = ""): Promise<string> {
58
+ if (NON_INTERACTIVE) return defaultValue
59
+
60
+ const hint = defaultValue ? ` [${defaultValue}]` : ""
61
+ process.stdout.write(`${message}${hint}: `)
62
+
63
+ return new Promise((resolve) => {
64
+ const timeout = setTimeout(() => {
65
+ process.stdin.removeAllListeners("data")
66
+ resolve(defaultValue)
67
+ }, 30_000)
68
+
69
+ process.stdin.once("data", (data) => {
70
+ clearTimeout(timeout)
71
+ const input = data.toString().trim()
72
+ resolve(input || defaultValue)
73
+ })
74
+ })
75
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * CLI Command interface
3
+ * Defines the contract for all CLI subcommands
4
+ */
5
+ export interface CliCommand {
6
+ name: string;
7
+ description: string;
8
+ execute: (args: string[]) => Promise<number>;
9
+ }
@@ -0,0 +1,3 @@
1
+ export { ArgusConfigSchema } from "./schema"
2
+ export type { ArgusConfig } from "./types"
3
+ export { loadArgusConfig } from "./loader"
@@ -0,0 +1,36 @@
1
+ import { homedir } from "node:os"
2
+ import { join } from "node:path"
3
+ import { ArgusConfigSchema } from "./schema"
4
+ import type { ArgusConfig } from "./types"
5
+ import { detectConfigFile, readJsoncFile } from "../shared/file-utils"
6
+ import { deepMerge } from "../shared/deep-merge"
7
+ import { createLogger } from "../shared/logger"
8
+
9
+ export function _mergeConfigs(
10
+ userRaw: Record<string, unknown> | null,
11
+ projectRaw: Record<string, unknown> | null,
12
+ ): ArgusConfig {
13
+ const logger = createLogger()
14
+ const merged = deepMerge(userRaw ?? {}, projectRaw ?? {})
15
+
16
+ const result = ArgusConfigSchema.safeParse(merged)
17
+ if (!result.success) {
18
+ logger.warn("Invalid argus config, using defaults:", result.error.message)
19
+ return ArgusConfigSchema.parse({})
20
+ }
21
+
22
+ return result.data
23
+ }
24
+
25
+ export function loadArgusConfig(projectDir: string): ArgusConfig {
26
+ const userConfigDir = join(homedir(), ".config", "opencode")
27
+ const userConfigInfo = detectConfigFile(userConfigDir)
28
+ const userRaw = userConfigInfo.path ? readJsoncFile(userConfigInfo.path) : null
29
+
30
+ const projectConfigInfo = detectConfigFile(projectDir)
31
+ const projectRaw = projectConfigInfo.path
32
+ ? readJsoncFile(projectConfigInfo.path)
33
+ : null
34
+
35
+ return _mergeConfigs(userRaw, projectRaw)
36
+ }
@@ -0,0 +1,82 @@
1
+ import { z } from "zod"
2
+
3
+ const AgentConfigSchema = z.object({
4
+ model: z.string().optional(),
5
+ permission: z.record(z.string(), z.any()).optional(),
6
+ tools: z.record(z.string(), z.boolean()).optional(),
7
+ })
8
+
9
+ const ToolsConfigSchema = z.object({
10
+ slitherPath: z.string().optional(),
11
+ forgePath: z.string().optional(),
12
+ })
13
+
14
+ const ScvdConfigSchema = z.object({
15
+ enabled: z.boolean().default(true),
16
+ apiUrl: z.string().default("https://api.scvd.dev"),
17
+ })
18
+
19
+ const KnowledgeConfigSchema = z.object({
20
+ scvd: ScvdConfigSchema.default({
21
+ enabled: true,
22
+ apiUrl: "https://api.scvd.dev",
23
+ }),
24
+ autoSync: z.boolean().default(true),
25
+ customSkillsDir: z.string().optional(),
26
+ })
27
+
28
+ const ReportingConfigSchema = z.object({
29
+ format: z.enum(["markdown"]).default("markdown"),
30
+ severityThreshold: z
31
+ .enum(["critical", "high", "medium", "low", "informational"])
32
+ .default("low"),
33
+ gasAnalysis: z.boolean().default(false),
34
+ })
35
+
36
+ const SolditConfigSchema = z.object({
37
+ enabled: z.boolean().default(true),
38
+ port: z.number().default(3000),
39
+ })
40
+
41
+ const BackgroundConfigSchema = z.object({
42
+ max_concurrent: z.number().positive().default(3),
43
+ })
44
+
45
+ export const ArgusConfigSchema = z.object({
46
+ agents: z
47
+ .object({
48
+ argus: AgentConfigSchema.default({}),
49
+ sentinel: AgentConfigSchema.default({}),
50
+ pythia: AgentConfigSchema.default({}),
51
+ scribe: AgentConfigSchema.default({}),
52
+ })
53
+ .default({
54
+ argus: {},
55
+ sentinel: {},
56
+ pythia: {},
57
+ scribe: {},
58
+ }),
59
+ tools: ToolsConfigSchema.default({}),
60
+ knowledge: KnowledgeConfigSchema.default({
61
+ scvd: {
62
+ enabled: true,
63
+ apiUrl: "https://api.scvd.dev",
64
+ },
65
+ autoSync: true,
66
+ }),
67
+ reporting: ReportingConfigSchema.default({
68
+ format: "markdown",
69
+ severityThreshold: "low",
70
+ gasAnalysis: false,
71
+ }),
72
+ solodit: SolditConfigSchema.default({
73
+ enabled: true,
74
+ port: 3000,
75
+ }),
76
+ disabled_hooks: z.array(z.string()).default([]),
77
+ hooks: z.record(z.string(), z.any()).default({}),
78
+ cli: z.record(z.string(), z.any()).default({}),
79
+ background: BackgroundConfigSchema.default({
80
+ max_concurrent: 3,
81
+ }),
82
+ })
@@ -0,0 +1,4 @@
1
+ import { z } from "zod"
2
+ import { ArgusConfigSchema } from "./schema"
3
+
4
+ export type ArgusConfig = z.infer<typeof ArgusConfigSchema>
@@ -0,0 +1,6 @@
1
+ export const DEFAULT_MODELS = {
2
+ argus: "anthropic/claude-opus-4-6",
3
+ sentinel: "anthropic/claude-sonnet-4-6",
4
+ pythia: "anthropic/claude-sonnet-4-6",
5
+ scribe: "anthropic/claude-sonnet-4-6",
6
+ } as const