mantiz-cli 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -0
- package/package.json +34 -0
- package/src/index.ts +194 -0
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# @mantiz/cli
|
|
2
|
+
|
|
3
|
+
**Mantiz CLI — AI lie detector for coding agents.**
|
|
4
|
+
|
|
5
|
+
Scan git diffs for AI agent cheating patterns — like a polygraph for your test suite.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @mantiz/cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or run without installation:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx @mantiz/cli
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Scan your current git diff
|
|
23
|
+
mantiz-scan
|
|
24
|
+
|
|
25
|
+
# Scan with JSON output (for CI)
|
|
26
|
+
mantiz-scan --json
|
|
27
|
+
|
|
28
|
+
# Scan a specific diff file
|
|
29
|
+
mantiz-scan --diff "$(cat my-diff.diff)"
|
|
30
|
+
|
|
31
|
+
# Cloud scan with API token
|
|
32
|
+
mantiz-scan --token mtz_abc123
|
|
33
|
+
|
|
34
|
+
# Help
|
|
35
|
+
mantiz-scan --help
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## CI/CD Integration
|
|
39
|
+
|
|
40
|
+
```yaml
|
|
41
|
+
# .github/workflows/mantiz.yml
|
|
42
|
+
name: Mantiz Scan
|
|
43
|
+
on: [pull_request]
|
|
44
|
+
jobs:
|
|
45
|
+
scan:
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/checkout@v4
|
|
49
|
+
- uses: actions/setup-node@v4
|
|
50
|
+
- run: npx @mantiz/cli --token ${{ secrets.MANTIZ_API_TOKEN }}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Get your API token at: https://mantiz-wine.vercel.app/settings
|
|
54
|
+
|
|
55
|
+
## Exit Codes
|
|
56
|
+
|
|
57
|
+
- `0` — All clean (Trust Score ≥ 70)
|
|
58
|
+
- `1` — Cheating detected (Trust Score < 70)
|
|
59
|
+
|
|
60
|
+
## Environment Variables
|
|
61
|
+
|
|
62
|
+
| Variable | Description |
|
|
63
|
+
|----------|-------------|
|
|
64
|
+
| `MANTIZ_API_TOKEN` | API token for cloud scan mode |
|
|
65
|
+
| `MANTIZ_API_URL` | API URL (default: https://mantiz-wine.vercel.app) |
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mantiz-cli",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Mantiz CLI — AI lie detector for coding agents. Scan git diffs for cheating patterns.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mantiz-scan": "./src/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"scan": "tsx src/index.ts"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"mantiz-core": "0.1.2",
|
|
19
|
+
"tsx": "^4.19.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "22.20.0",
|
|
23
|
+
"typescript": "^6.0.2"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"ai",
|
|
27
|
+
"lie-detector",
|
|
28
|
+
"cheating",
|
|
29
|
+
"test",
|
|
30
|
+
"mantiz",
|
|
31
|
+
"cli"
|
|
32
|
+
],
|
|
33
|
+
"license": "MIT"
|
|
34
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Mantiz CLI — AI Lie Detector for Coding Agents
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* mantiz-scan # Scan local git diff
|
|
7
|
+
* mantiz-scan --diff <str> # Scan provided diff text
|
|
8
|
+
* mantiz-scan --token x # Send to Mantiz API for cloud scan
|
|
9
|
+
* mantiz-scan --help # Show help
|
|
10
|
+
*
|
|
11
|
+
* Install:
|
|
12
|
+
* npm install -g @mantiz/cli
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { execSync } from 'node:child_process'
|
|
16
|
+
import { scanDiff, type ScanResult } from 'mantiz-core'
|
|
17
|
+
|
|
18
|
+
const PASS_THRESHOLD = 70
|
|
19
|
+
|
|
20
|
+
function getGitDiff(): string {
|
|
21
|
+
try {
|
|
22
|
+
const diff = execSync('git diff', { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 })
|
|
23
|
+
if (diff.trim()) return diff
|
|
24
|
+
const staged = execSync('git diff --staged', { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 })
|
|
25
|
+
if (staged.trim()) return staged
|
|
26
|
+
const head = execSync('git diff HEAD~1', { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 })
|
|
27
|
+
return head
|
|
28
|
+
} catch {
|
|
29
|
+
return ''
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function printResults(result: ScanResult): void {
|
|
34
|
+
const scoreColor = result.trustScore >= 80 ? '\x1b[32m' : result.trustScore >= 50 ? '\x1b[33m' : '\x1b[31m'
|
|
35
|
+
const scoreLabel = result.trustScore >= 80 ? 'CLEAN ✅' : result.trustScore >= 50 ? 'SUSPICIOUS 🟡' : 'CHEATING DETECTED 🔴'
|
|
36
|
+
const reset = '\x1b[0m'
|
|
37
|
+
const bold = '\x1b[1m'
|
|
38
|
+
const dim = '\x1b[2m'
|
|
39
|
+
|
|
40
|
+
console.log('\n' + '='.repeat(50))
|
|
41
|
+
console.log(`${bold}🔍 MANTIZ SCAN RESULTS${reset}`)
|
|
42
|
+
console.log('='.repeat(50))
|
|
43
|
+
console.log(`\n${bold}Trust Score:${reset} ${scoreColor}${result.trustScore}/100${reset} ${scoreLabel}`)
|
|
44
|
+
console.log(`${dim}Threshold:${reset} ${PASS_THRESHOLD}${dim} (scores below this will fail)${reset}`)
|
|
45
|
+
console.log(`\n${bold}Summary:${reset}`)
|
|
46
|
+
console.log(` Findings: ${result.summary.totalFindings}`)
|
|
47
|
+
console.log(` Files: ${result.summary.filesScanned}`)
|
|
48
|
+
console.log(` Verdict: ${scoreColor}${scoreLabel}${reset}`)
|
|
49
|
+
|
|
50
|
+
if (result.findings.length > 0) {
|
|
51
|
+
console.log(`\n${bold}Findings:${reset}`)
|
|
52
|
+
for (const f of result.findings) {
|
|
53
|
+
const confColor = f.confidence === 'high' ? '\x1b[31m' : f.confidence === 'medium' ? '\x1b[33m' : '\x1b[90m'
|
|
54
|
+
console.log(` ${confColor}${f.confidence.toUpperCase()}${reset} ${f.filePath}:${f.lineStart}`)
|
|
55
|
+
console.log(` ${dim}${f.explanation}${reset}`)
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
console.log(`\n ${bold}No cheating detected.${reset} ${dim}Code looks honest.${reset}`)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (result.fixInstructions.length > 0) {
|
|
62
|
+
console.log(`\n${bold}Fix Instructions:${reset}`)
|
|
63
|
+
for (const fi of result.fixInstructions) {
|
|
64
|
+
console.log(` [${fi.patternType}] ${fi.instruction}`)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
console.log(`\n${dim}${'='.repeat(50)}${reset}\n`)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function printHelp(): void {
|
|
72
|
+
console.log(`
|
|
73
|
+
Mantiz CLI — AI Lie Detector for Coding Agents
|
|
74
|
+
|
|
75
|
+
USAGE
|
|
76
|
+
mantiz-scan Scan current git diff
|
|
77
|
+
mantiz-scan --diff <text> Scan provided diff text
|
|
78
|
+
mantiz-scan --token <key> Send to Mantiz cloud API
|
|
79
|
+
mantiz-scan --json Output results as JSON
|
|
80
|
+
mantiz-scan --help Show this help
|
|
81
|
+
|
|
82
|
+
EXIT CODES
|
|
83
|
+
0 — All clean (Trust Score >= ${PASS_THRESHOLD})
|
|
84
|
+
1 — Cheating detected (Trust Score < ${PASS_THRESHOLD})
|
|
85
|
+
|
|
86
|
+
ENVIRONMENT VARIABLES
|
|
87
|
+
MANTIZ_API_TOKEN API token for cloud scanning
|
|
88
|
+
MANTIZ_API_URL API URL (default: https://mantiz-wine.vercel.app)
|
|
89
|
+
|
|
90
|
+
EXAMPLES
|
|
91
|
+
mantiz-scan
|
|
92
|
+
cat my-diff.txt | mantiz-scan --diff -
|
|
93
|
+
mantiz-scan --json | jq '.trustScore'
|
|
94
|
+
mantiz-scan --token mtz_abc123
|
|
95
|
+
`)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function main(): Promise<void> {
|
|
99
|
+
const args = process.argv.slice(2)
|
|
100
|
+
|
|
101
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
102
|
+
printHelp()
|
|
103
|
+
process.exit(0)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const jsonOutput = args.includes('--json')
|
|
107
|
+
const tokenIndex = args.indexOf('--token')
|
|
108
|
+
const token = tokenIndex !== -1 ? args[tokenIndex + 1] : process.env.MANTIZ_API_TOKEN
|
|
109
|
+
const diffIndex = args.indexOf('--diff')
|
|
110
|
+
const diffArg = diffIndex !== -1 ? args[diffIndex + 1] : undefined
|
|
111
|
+
|
|
112
|
+
let diffText: string
|
|
113
|
+
if (diffArg) {
|
|
114
|
+
diffText = diffArg === '-' ? execSync('cat', { encoding: 'utf-8' }) : diffArg
|
|
115
|
+
} else {
|
|
116
|
+
diffText = getGitDiff()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!diffText || !diffText.trim()) {
|
|
120
|
+
if (jsonOutput) {
|
|
121
|
+
console.log(JSON.stringify({ error: 'No git diff found', trustScore: 0 }))
|
|
122
|
+
} else {
|
|
123
|
+
console.log('\x1b[33m⚠️ No git diff found. Run `git add` first or make some changes.\x1b[0m')
|
|
124
|
+
}
|
|
125
|
+
process.exit(1)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (token) {
|
|
129
|
+
const apiUrl = process.env.MANTIZ_API_URL || 'https://mantiz-wine.vercel.app'
|
|
130
|
+
try {
|
|
131
|
+
const res = await fetch(`${apiUrl}/api/scan`, {
|
|
132
|
+
method: 'POST',
|
|
133
|
+
headers: {
|
|
134
|
+
'Content-Type': 'application/json',
|
|
135
|
+
'Authorization': `Bearer ${token}`,
|
|
136
|
+
},
|
|
137
|
+
body: JSON.stringify({ diff: diffText }),
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
if (!res.ok) {
|
|
141
|
+
const errBody = await res.text()
|
|
142
|
+
if (jsonOutput) {
|
|
143
|
+
console.log(JSON.stringify({ error: `API error: ${res.status}`, trustScore: 0 }))
|
|
144
|
+
} else {
|
|
145
|
+
console.log(`\x1b[31mAPI error: ${res.status} — ${errBody}\x1b[0m`)
|
|
146
|
+
}
|
|
147
|
+
process.exit(1)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const result = await res.json() as { trustScore: number; findings: any[]; summary: any }
|
|
151
|
+
|
|
152
|
+
if (jsonOutput) {
|
|
153
|
+
console.log(JSON.stringify(result, null, 2))
|
|
154
|
+
} else {
|
|
155
|
+
const scoreColor = result.trustScore >= 80 ? '\x1b[32m' : '\x1b[33m'
|
|
156
|
+
console.log(`\n${scoreColor}Trust Score: ${result.trustScore}/100\x1b[0m`)
|
|
157
|
+
console.log(`Findings: ${result.findings.length}`)
|
|
158
|
+
result.findings.slice(0, 5).forEach((f: any) => {
|
|
159
|
+
console.log(` [${f.confidence}] ${f.filePath}:${f.lineStart} — ${f.explanation}`)
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
process.exit(result.trustScore < PASS_THRESHOLD ? 1 : 0)
|
|
164
|
+
} catch (err) {
|
|
165
|
+
if (jsonOutput) {
|
|
166
|
+
console.log(JSON.stringify({ error: `Failed to reach Mantiz API: ${err}`, trustScore: 0 }))
|
|
167
|
+
} else {
|
|
168
|
+
console.log(`\x1b[31mFailed to reach Mantiz API: ${err}\x1b[0m`)
|
|
169
|
+
}
|
|
170
|
+
process.exit(1)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const result = scanDiff(diffText)
|
|
175
|
+
|
|
176
|
+
if (jsonOutput) {
|
|
177
|
+
console.log(JSON.stringify({
|
|
178
|
+
trustScore: result.trustScore,
|
|
179
|
+
summary: result.summary,
|
|
180
|
+
findings: result.findings,
|
|
181
|
+
fixInstructions: result.fixInstructions,
|
|
182
|
+
passed: result.trustScore >= PASS_THRESHOLD,
|
|
183
|
+
}, null, 2))
|
|
184
|
+
} else {
|
|
185
|
+
printResults(result)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
process.exit(result.trustScore < PASS_THRESHOLD ? 1 : 0)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
main().catch((err) => {
|
|
192
|
+
console.error('Fatal error:', err)
|
|
193
|
+
process.exit(1)
|
|
194
|
+
})
|