Skip to content

Commit 8ca1b9b

Browse files
Merge pull request #27 from CodeAnt-AI/multiples-changes
Multiples changes
2 parents f875c2e + 0f42550 commit 8ca1b9b

13 files changed

Lines changed: 127 additions & 38 deletions

File tree

.github/workflows/publish.yml

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,18 @@ jobs:
1717
- uses: actions/setup-node@v4
1818
with:
1919
node-version: '20'
20+
registry-url: 'https://registry.npmjs.org'
2021

2122
- id: version
2223
run: echo "version=$(jq -r .version package.json)" >> "$GITHUB_OUTPUT"
2324

2425
- run: npm ci
2526

27+
- run: npm publish --access public
28+
env:
29+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
30+
2631
- run: npm pack
27-
# produces codeant-cli-<version>.tgz in the working directory
2832

2933
- uses: actions/upload-artifact@v4
3034
with:
@@ -43,13 +47,3 @@ jobs:
4347
4448
Commit: ${{ github.sha }}
4549
Message: ${{ github.event.head_commit.message }}
46-
47-
## Publish to npm
48-
49-
Download the `.tgz` and run:
50-
51-
```
52-
npm publish codeant-cli-${{ steps.version.outputs.version }}.tgz --access public
53-
```
54-
55-
(Requires npm auth with publish rights on the `codeant-cli` package.)

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Thumbs.db
2323
# Build output
2424
dist/
2525
build/
26+
tmp/
2627

2728
# Yarn lock (if using npm)
2829
yarn.lock

mcpb/manifest.json

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
"manifest_version": "0.3",
33
"name": "codeant",
44
"display_name": "CodeAnt AI",
5-
"version": "0.5.0",
5+
"version": "0.5.1",
66
"description": "Drive CodeAnt AI security scans and code review from Claude — org-wide secret triage, cross-repo SAST/SCA findings, on-demand scans, and local PR review.",
7-
"long_description": "CodeAnt AI inside Claude. Ask things like \"how many critical SAST findings do I have across my org?\", \"show every exposed secret in payments-service\", or \"review my staged changes\" — Claude calls the CodeAnt API directly via this MCP server.\n\nIncludes 11 read-only tools (orgs, repos, scan history, scan metadata, findings, dismissed alerts, PRs, comments, comment search, local review) and 2 opt-in write tools (trigger a scan, resolve a PR conversation) gated behind a setting.\n\nRequires a CodeAnt account. Sign up at https://codeant.ai and grab an API key from your settings page.",
7+
"long_description": "CodeAnt AI inside Claude. Ask things like \"how many critical SAST findings do I have across my org?\", \"show every exposed secret in payments-service\", or \"review my staged changes\" — Claude calls the CodeAnt API directly via this MCP server.\n\nIncludes 11 read-only tools (orgs, repos, scan history, scan metadata, findings, dismissed alerts, PRs, comments, comment search, local review) and 2 opt-in write tools (trigger a scan, resolve a PR conversation) gated behind a setting.\n\nRequires a CodeAnt account. Sign up at https://codeant.ai. To authenticate, call the `codeant_login` tool — it opens the CodeAnt sign-in page in your browser and saves the token automatically.\n\nCollects anonymous usage telemetry via PostHog by default; set CODEANT_TELEMETRY_DISABLED=1 to opt out.",
88
"author": {
99
"name": "CodeAnt AI",
1010
"email": "support@codeant.ai",
@@ -28,7 +28,7 @@
2828
"pull-requests"
2929
],
3030
"privacy_policies": [
31-
"https://codeant.ai/privacy"
31+
"https://www.codeant.ai/privacy-policy"
3232
],
3333
"icon": "icon.png",
3434
"server": {
@@ -40,7 +40,15 @@
4040
"env": {
4141
"CODEANT_API_TOKEN": "${user_config.api_token}",
4242
"CODEANT_API_URL": "${user_config.base_url}",
43-
"CODEANT_READ_ONLY": "${user_config.read_only}"
43+
"CODEANT_READ_ONLY": "${user_config.read_only}",
44+
"GITHUB_TOKEN": "${user_config.github_token}",
45+
"GITLAB_TOKEN": "${user_config.gitlab_token}",
46+
"BITBUCKET_TOKEN": "${user_config.bitbucket_token}",
47+
"AZURE_DEVOPS_TOKEN": "${user_config.azure_devops_token}",
48+
"GITHUB_API_URL": "${user_config.github_api_url}",
49+
"GITLAB_URL": "${user_config.gitlab_url}",
50+
"BITBUCKET_URL": "${user_config.bitbucket_url}",
51+
"AZURE_DEVOPS_ORG_URL": "${user_config.azure_devops_org_url}"
4452
}
4553
}
4654
},
@@ -63,14 +71,15 @@
6371
{ "name": "codeant_comments_search", "description": "Search across CodeAnt review comments by free-text query." },
6472
{ "name": "codeant_review_local", "description": "Run a CodeAnt AI review on local working-copy changes." },
6573
{ "name": "codeant_login", "description": "Open app.codeant.ai in the browser and poll until the user completes sign-in; saves the resulting API token." },
74+
{ "name": "codeant_logout", "description": "Clear the saved API token and sign out of CodeAnt AI." },
6675
{ "name": "codeant_scans_start", "description": "Trigger a new scan run (write — gated behind read_only=false)." },
6776
{ "name": "codeant_pr_resolve", "description": "Resolve a PR conversation thread (write — gated behind read_only=false)." }
6877
],
6978
"user_config": {
7079
"api_token": {
7180
"type": "string",
7281
"title": "CodeAnt API token",
73-
"description": "Optional. Leave blank and run the `codeant_login` tool to sign in through your browser instead. Otherwise paste a token from your CodeAnt account settings at app.codeant.ai.",
82+
"description": "Optional. Leave blank and call the `codeant_login` tool to sign in through your browser.",
7483
"required": false,
7584
"sensitive": true,
7685
"default": ""
@@ -88,6 +97,66 @@
8897
"description": "When enabled, write tools (trigger scan, resolve PR thread) are hidden. Recommended.",
8998
"required": false,
9099
"default": true
100+
},
101+
"github_token": {
102+
"type": "string",
103+
"title": "GitHub token",
104+
"description": "Personal access token for GitHub. Also accepts the GH_TOKEN environment variable. Leave blank to fall back to the gh CLI.",
105+
"required": false,
106+
"sensitive": true,
107+
"default": ""
108+
},
109+
"gitlab_token": {
110+
"type": "string",
111+
"title": "GitLab token",
112+
"description": "Personal access token for GitLab. Leave blank to fall back to the glab CLI.",
113+
"required": false,
114+
"sensitive": true,
115+
"default": ""
116+
},
117+
"bitbucket_token": {
118+
"type": "string",
119+
"title": "Bitbucket token",
120+
"description": "App password or access token for Bitbucket.",
121+
"required": false,
122+
"sensitive": true,
123+
"default": ""
124+
},
125+
"azure_devops_token": {
126+
"type": "string",
127+
"title": "Azure DevOps token",
128+
"description": "Personal access token for Azure DevOps. Also accepts AZURE_DEVOPS_PAT.",
129+
"required": false,
130+
"sensitive": true,
131+
"default": ""
132+
},
133+
"github_api_url": {
134+
"type": "string",
135+
"title": "GitHub API URL",
136+
"description": "Override for GitHub Enterprise Server (e.g. https://github.example.com/api/v3). Leave blank for GitHub.com.",
137+
"required": false,
138+
"default": ""
139+
},
140+
"gitlab_url": {
141+
"type": "string",
142+
"title": "GitLab URL",
143+
"description": "Override for a self-hosted GitLab instance (e.g. https://gitlab.example.com). Leave blank for GitLab.com.",
144+
"required": false,
145+
"default": ""
146+
},
147+
"bitbucket_url": {
148+
"type": "string",
149+
"title": "Bitbucket URL",
150+
"description": "Override for a self-hosted Bitbucket Server instance. Leave blank for Bitbucket Cloud.",
151+
"required": false,
152+
"default": ""
153+
},
154+
"azure_devops_org_url": {
155+
"type": "string",
156+
"title": "Azure DevOps organization URL",
157+
"description": "Full URL to your Azure DevOps organization (e.g. https://dev.azure.com/myorg).",
158+
"required": false,
159+
"default": ""
91160
}
92161
}
93162
}

src/components/ScanCenter.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ export default function ScanCenter({ filterDismissed = false, includeFalsePositi
190190

191191
// ── Step 3: repo selected → fetch scan history ──
192192
const handleSelectRepo = (item) =>
193-
_handleSelectRepo({ STEPS, item, setSelectedRepo, setStep, setLoadingMsg, setError, setScanHistory });
193+
_handleSelectRepo({ STEPS, item, selectedConnection, setSelectedRepo, setStep, setLoadingMsg, setError, setScanHistory });
194194

195195
// ── Step 4: scan selected → show result type menu ──
196196
const handleSelectScan = (item) =>

src/mcp/server.js

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { runStartScan } from '../commands/scans/start-scan.js';
1313
import { runReviewHeadless } from '../reviewHeadless.js';
1414
import * as scm from '../scm/index.js';
1515
import { isAlreadyLoggedIn, runLoginFlow } from '../utils/loginFlow.js';
16-
import { getConfigValue } from '../utils/config.js';
16+
import { getConfigValue, setConfigValue } from '../utils/config.js';
1717

1818
const require = createRequire(import.meta.url);
1919
const pkg = require('../../package.json');
@@ -73,13 +73,7 @@ async function ensureAuthenticated() {
7373
return;
7474
}
7575

76-
console.error('[codeant-mcp] No API token configured — opening browser for sign-in.');
77-
try {
78-
await runLoginFlow();
79-
console.error('[codeant-mcp] Login complete.');
80-
} catch (err) {
81-
console.error(`[codeant-mcp] Login failed: ${err.message}. The server will start anyway; call the codeant_login tool to retry.`);
82-
}
76+
console.error('[codeant-mcp] No API token configured. Call the codeant_login tool to sign in, or set CODEANT_API_TOKEN.');
8377
}
8478

8579
export async function startMcpServer() {
@@ -415,6 +409,24 @@ export async function startMcpServer() {
415409
}
416410
);
417411

412+
server.registerTool(
413+
'codeant_logout',
414+
{
415+
title: 'Sign out of CodeAnt AI',
416+
description: 'Clears the saved API token from ~/.codeant/config.json and unsets CODEANT_API_TOKEN on the running MCP process. Returns { wasLoggedIn: false } immediately if no token was configured.',
417+
inputSchema: {},
418+
annotations: { ...WRITE_NON_DESTRUCTIVE, idempotentHint: true },
419+
},
420+
async () => {
421+
try {
422+
const wasLoggedIn = !!(process.env.CODEANT_API_TOKEN?.trim() || getConfigValue('apiKeyV2'));
423+
setConfigValue('apiKeyV2', null);
424+
delete process.env.CODEANT_API_TOKEN;
425+
return ok({ wasLoggedIn, status: wasLoggedIn ? 'logged_out' : 'not_logged_in' });
426+
} catch (err) { return fail(err); }
427+
}
428+
);
429+
418430
// ─── Write-side tools (gated behind CODEANT_READ_ONLY=0) ─────────────────
419431
if (!readOnly) {
420432
server.registerTool(

src/rules/secrets.toml

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4071,16 +4071,6 @@ regex = '''(?i)\b(phx_[a-zA-Z0-9_\-]{47})(?:\\?['"\x60]|[\s;]|\\[nr]|$)'''
40714071
entropy = 3
40724072
keywords = ["phx_"]
40734073

4074-
# ──────────────────────────────────────────────────────────────────────────────
4075-
# posthog-project-api-key
4076-
# ──────────────────────────────────────────────────────────────────────────────
4077-
[[rules]]
4078-
id = "posthog-project-api-key"
4079-
description = "Detected a PostHog Project API Key, which may expose product analytics data and event tracking to unauthorized access."
4080-
regex = '''(?i)\b(phc_[a-zA-Z0-9_\-]{43})(?:\\?['"\x60]|[\s;]|\\[nr]|$)'''
4081-
entropy = 3
4082-
keywords = ["phc_"]
4083-
40844074
# ──────────────────────────────────────────────────────────────────────────────
40854075
# postman-api-token
40864076
# ──────────────────────────────────────────────────────────────────────────────

src/scanCenter/handleSelectRepo.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
import { getScanHistory } from '../scans/getScanHistory.js';
22

3-
export async function handleSelectRepo({ STEPS, item, setSelectedRepo, setStep, setLoadingMsg, setError, setScanHistory }) {
3+
export async function handleSelectRepo({ STEPS, item, selectedConnection, setSelectedRepo, setStep, setLoadingMsg, setError, setScanHistory }) {
44
setSelectedRepo(item.value);
55
setStep(STEPS.LOADING);
6-
const repoFullName = item.value.full_name || item.value.name;
6+
const orgName = selectedConnection?.organizationName;
7+
const repoName = item.value.name;
8+
const repoFullName = item.value.full_name || (orgName && repoName ? `${orgName}/${repoName}` : repoName);
9+
if (!repoFullName || !repoFullName.includes('/')) {
10+
setError(`Cannot resolve repository in org/repo form (got "${repoFullName}")`, STEPS.SELECT_REPO);
11+
return;
12+
}
713
setLoadingMsg(`Loading scan history for ${repoFullName}…`);
814
const res = await getScanHistory(repoFullName);
915
if (!res.success) {

src/tools/globTool.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import path from 'path';
2+
import { assertInsideCwd } from './pathUtils.js';
23

34
export async function globTool(args, cwd) {
45
const { globSync } = await import('glob');
56
const pattern = path.resolve(cwd, args.pattern);
7+
assertInsideCwd(pattern.replace(/[*?{[\\].*$/, '') || cwd, cwd);
68
const matches = globSync(pattern);
79
if (!matches.length) return 'No files found';
810
return matches.map(m => path.relative(cwd, m)).join('\n');

src/tools/grepTool.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { spawn } from 'child_process';
22
import path from 'path';
3+
import { assertInsideCwd } from './pathUtils.js';
34

45
export async function grepTool(args, cwd) {
56
const target = args.path ? path.resolve(cwd, args.path) : cwd;
7+
assertInsideCwd(target, cwd);
68
const result = await new Promise((resolve) => {
79
const proc = spawn('grep', ['-rn', args.pattern, target], {
810
cwd,

src/tools/lsTool.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import fs from 'fs';
22
import path from 'path';
3+
import { assertInsideCwd } from './pathUtils.js';
34

45
export async function lsTool(args, cwd) {
56
const dirPath = args.path ? path.resolve(cwd, args.path) : cwd;
7+
assertInsideCwd(dirPath, cwd);
68
return fs.readdirSync(dirPath).sort().join('\n');
79
}

0 commit comments

Comments
 (0)