Compare commits

...

10 Commits

Author SHA1 Message Date
Renee Huang 8915aab20b merge 2026-05-19 16:41:40 -07:00
Renee Huang e44f9b1697 add more iterations 2026-05-19 16:06:29 -07:00
Renee Huang 960ff55f83 add clinecore quickstart example 2026-05-19 15:53:34 -07:00
Saoud Rizwan 07e2d0d3f8 fix: improve security review bot streaming (#10889)
Co-authored-by: Renee Huang <100229782+reneehuang1@users.noreply.github.com>
2026-05-19 08:44:22 -07:00
Renee Huang ba471b7db9 clarifications 2026-05-18 21:44:27 -07:00
Renee Huang 72e2c8de2b Merge branch 'main' of github.com:cline/cline into renee/more-updates 2026-05-18 21:43:25 -07:00
Renee Huang 5e7257d218 try to make it work; streaming doesnt work yet 2026-05-18 19:06:48 -07:00
Renee Huang dcef5b29d1 switch to clinecore runtime 2026-05-18 17:34:37 -07:00
Renee Huang 6b530e5950 initial attempt; needs more thoughts 2026-05-18 14:55:48 -07:00
Renee Huang 26daf9cb48 fix to have the example run 2026-05-18 14:28:18 -07:00
14 changed files with 802 additions and 10 deletions
+2
View File
@@ -32,6 +32,7 @@ Requires Node.js 22+.
| Example | Description | Concepts |
|---------|-------------|----------|
| [quickstart](./quickstart) | Send one prompt, stream the response. ~15 lines of code. | `Agent`, `subscribe`, `run()` |
| [quickstart-clinecore](./quickstart-clinecore) | Send one prompt through a persisted Core session. | `ClineCore`, `start()`, `CoreSessionEvent`, `dispose()` |
| [cli-agent](./cli-agent) | Interactive terminal chat with a shell tool. | `createTool`, multi-turn `run()`/`continue()`, streaming |
| [cline-core-cli-agent](./cline-core-cli-agent) | Interactive terminal chat powered by ClineCore. | `ClineCore.create()`, `cline.start()`, `cline.send()`, built-in tools, streaming |
@@ -40,6 +41,7 @@ Requires Node.js 22+.
| Example | Description | Concepts |
|---------|-------------|----------|
| [code-review-bot](./code-review-bot) | AI code reviewer that reads git diffs and produces structured comments. | Multiple tools, `completesRun` lifecycle, `systemPrompt`, zod schemas |
| [security-review-bot](./security-review-bot) | AI application security reviewer that reads git diffs and produces structured security findings. | Specialized agent prompts, multiple tools, security schemas, `completesRun` lifecycle |
| [multi-agent](./multi-agent) | Web app that fans out to three specialist agents in parallel, streams results via SSE, then synthesizes a unified answer. | Concurrent agents, `Promise.all`, per-agent `subscribe()`, SSE streaming, agent composition |
### Advanced
@@ -167,7 +167,20 @@ fn push_notification(
}
fn resolve_sidecar_script(workspace_root: &str, launch_cwd: &str) -> Option<PathBuf> {
let candidates = [
sidecar_script_candidates(workspace_root, launch_cwd)
.into_iter()
.find(|p| p.exists())
}
fn sidecar_script_candidates(workspace_root: &str, launch_cwd: &str) -> Vec<PathBuf> {
vec![
PathBuf::from(workspace_root)
.join("sdk")
.join("apps")
.join("examples")
.join("menubar")
.join("sidecar")
.join("index.ts"),
PathBuf::from(workspace_root)
.join("sdk")
.join("apps")
@@ -182,8 +195,7 @@ fn resolve_sidecar_script(workspace_root: &str, launch_cwd: &str) -> Option<Path
.join("sidecar")
.join("index.ts"),
PathBuf::from(launch_cwd).join("sidecar").join("index.ts"),
];
candidates.into_iter().find(|p| p.exists())
]
}
fn sidecar_binary_name() -> String {
@@ -261,8 +273,13 @@ fn start_sidecar(
.current_dir(workspace_root);
c
} else {
let searched_paths = sidecar_script_candidates(workspace_root, launch_cwd)
.into_iter()
.map(|path| path.display().to_string())
.collect::<Vec<_>>()
.join(", ");
return Err(format!(
"menubar sidecar not found under workspace_root={workspace_root}"
"menubar sidecar not found under workspace_root={workspace_root}; searched: {searched_paths}"
));
};
@@ -0,0 +1,63 @@
# Quickstart with ClineCore
A minimal Cline SDK example that uses `ClineCore` instead of the lightweight `Agent` runtime. It starts one local Core session, sends a single prompt, streams the assistant text to stdout, prints token usage, and then disposes the Core runtime.
## Getting started
Use Node.js 22 or newer.
Install dependencies and build the SDK packages:
```bash
bun install
bun run build:sdk
```
Set an API key:
```bash
export CLINE_API_KEY="cline_..."
```
Run:
```bash
bun dev
```
Or pass a prompt from the command line:
```bash
bun dev "Compare Agent and ClineCore in a markdown table."
```
## What it does
1. Creates a local `ClineCore` runtime with `ClineCore.create()`
2. Subscribes to `CoreSessionEvent` events with `cline.subscribe()`
3. Reads a prompt from command-line arguments, falling back to the default prompt
4. Starts a non-interactive session with `cline.start()`
5. Renders assistant text from nested `agent_event` events
6. Prints usage plus the persisted session ID
7. Calls `cline.dispose()` to clean up runtime resources
## Agent vs ClineCore
The original [quickstart](../quickstart) uses `Agent`, while this example uses `ClineCore`.
| Area | `Agent` | `ClineCore` |
| --- | --- | --- |
| Best for | Simple in-process agent loops, custom tools, browser-compatible use cases | Full Cline runtime sessions, workspace-aware tools, persistence, automation, and multi-client/hub scenarios |
| Construction | `new Agent({ providerId, modelId, apiKey })` | `await ClineCore.create({ clientName, backendMode })` |
| Running | `await agent.run(prompt)` | `await cline.start({ prompt, config })` |
| Events | `agent.subscribe()` emits `AgentRuntimeEvent` directly, e.g. `assistant-text-delta` | `cline.subscribe()` emits `CoreSessionEvent`; assistant events are usually nested under `event.type === "agent_event"` |
| Result text | `result.outputText` | Session result is under `result.result`; stream text comes from events |
| Tools | You provide tools explicitly with `tools` | Can use ClineCore built-in tools (`read_files`, `search_codebase`, shell/editor tools when enabled) and custom `extraTools` |
| Persistence | Lightweight runtime state only | Creates persisted sessions with IDs, manifests, message artifacts, and history support |
| Cleanup | Usually no explicit disposal needed for the simple runtime | Always call `await cline.dispose()` when done |
Use `Agent` when you want the smallest API surface and own all tools/context yourself. Use `ClineCore` when you want the SDK to manage Cline-like sessions, workspace context, built-in tools, persistence, hub/remote runtime options, or automation.
## Notes
This quickstart disables built-in tools (`enableTools: false`) to keep behavior close to the original one-prompt example. To explore ClineCore's workspace tools, enable tools and set tool policies appropriate for your app.
@@ -0,0 +1,21 @@
{
"name": "@cline/example-quickstart-clinecore",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "bun run src/index.ts",
"build:sdk": "bun run --cwd ../../.. build:sdk",
"build": "tsc",
"start": "node dist/index.js"
},
"dependencies": {
"@cline/sdk": "workspace:*"
},
"devDependencies": {
"typescript": "^5.9.3"
},
"engines": {
"node": ">=22"
}
}
@@ -0,0 +1,54 @@
import { type AgentEvent, ClineCore } from "@cline/sdk";
const defaultPrompt = "Explain what an SDK is in two sentences.";
const prompt = process.argv.slice(2).join(" ").trim() || defaultPrompt;
const cline = await ClineCore.create({
clientName: "quickstart-clinecore",
backendMode: "local",
});
const unsubscribe = cline.subscribe((event) => {
if (event.type === "agent_event") {
renderAgentEvent(event.payload.event);
}
});
try {
const result = await cline.start({
source: "cli",
interactive: false,
prompt,
config: {
providerId: "cline",
modelId: "anthropic/claude-sonnet-4.6",
apiKey: process.env.CLINE_API_KEY,
cwd: process.cwd(),
workspaceRoot: process.cwd(),
mode: "act",
systemPrompt: "You are a helpful assistant. Be concise.",
maxIterations: 10,
enableTools: true,
enableSpawnAgent: false,
enableAgentTeams: false,
disableMcpSettingsTools: true,
},
});
const usage = result.result?.usage;
console.log(
`\n\nDone (${result.result?.iterations ?? 0} iteration, ${usage?.outputTokens ?? 0} output tokens)`,
);
console.log(`Session: ${result.sessionId}`);
} finally {
unsubscribe();
await cline.dispose();
}
function renderAgentEvent(event: AgentEvent) {
if (event.type !== "content_start" || event.contentType !== "text") {
return;
}
process.stdout.write(event.text ?? "");
}
@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.apps.json",
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"outDir": "dist",
"strict": true,
"skipLibCheck": true,
"esModuleInterop": true
},
"include": ["src"]
}
+9 -2
View File
@@ -25,12 +25,19 @@ Run:
bun dev
```
Or pass a prompt from the command line:
```bash
bun dev "Explain event streaming in AI apps to a frontend engineer."
```
## What it does
1. Creates an `Agent` with a provider and model
2. Subscribes to `assistant-text-delta` events to stream output
3. Calls `agent.run()` with a prompt
4. Prints token usage when done
3. Reads a prompt from command-line arguments, falling back to the default prompt
4. Calls `agent.run()` with that prompt
5. Prints token usage when done
## Notes
+4 -1
View File
@@ -1,5 +1,8 @@
import { Agent } from "@cline/sdk";
const defaultPrompt = "Explain what an SDK is in two sentences.";
const prompt = process.argv.slice(2).join(" ").trim() || defaultPrompt;
const agent = new Agent({
providerId: "cline",
modelId: "anthropic/claude-sonnet-4.6",
@@ -13,7 +16,7 @@ agent.subscribe((event) => {
}
});
const result = await agent.run("Explain what an SDK is in two sentences.");
const result = await agent.run(prompt);
console.log(
`\n\nDone (${result.iterations} iteration, ${result.usage.outputTokens} output tokens)`,
);
@@ -0,0 +1,135 @@
# Security Review Bot on git diffs
An AI-powered application security review agent that reads a git diff, analyzes it for security risk, and produces structured findings with severity, category, CWE/OWASP metadata, exploit scenarios, remediation guidance, and confidence levels.
This example is based on the `code-review-bot` example, but uses the `ClineCore` runtime instead of the lightweight `Agent` runtime. That means it can use ClineCore's built-in workspace tools, including `read_files` and `search_codebase`, while still adding custom security review tools through `extraTools`.
## Getting started
Install dependencies:
```bash
bun install
bun run build:sdk
```
Set an API key:
```bash
export CLINE_API_KEY="cline_..."
```
Security review the last commit:
```bash
bun dev
```
Security review against a specific ref:
```bash
bun dev main
bun dev HEAD~5
bun dev abc123
```
Add run-specific review instructions with `--prompt` or `-p`:
```bash
bun dev main --prompt "Focus on authorization bypasses and SSRF"
bun dev HEAD~1 -p "Treat test-only secrets as low severity"
```
You can also pass extra positional text after the ref; it will be appended as additional review instructions:
```bash
bun dev main "Prioritize auth, secrets, and unsafe file access"
```
## Review a specific repo
The bot reviews the git repository in the current working directory. To review a different repo, run the example command from that repo's directory and pass the ref you want to compare against.
Using Bun directly from another repo:
```bash
cd /path/to/repo-you-want-to-review
bun --cwd /path/to/cline/sdk/apps/examples/security-review-bot dev main
```
With extra instructions:
```bash
cd /path/to/repo-you-want-to-review
bun --cwd /path/to/cline/sdk/apps/examples/security-review-bot dev main --prompt "Focus on payment and admin flows"
```
Or build the example once and run the compiled JavaScript from any git repo:
```bash
cd /path/to/cline/sdk/apps/examples/security-review-bot
bun run build
cd /path/to/repo-you-want-to-review
node /path/to/cline/sdk/apps/examples/security-review-bot/dist/index.js main
```
The argument is passed to `git diff <ref>` in the target repo. Common examples:
```bash
# Review all changes on your branch compared with main
bun --cwd /path/to/cline/sdk/apps/examples/security-review-bot dev main
# Review the last commit
bun --cwd /path/to/cline/sdk/apps/examples/security-review-bot dev HEAD~1
# Review changes since a release tag
bun --cwd /path/to/cline/sdk/apps/examples/security-review-bot dev v1.2.3
```
If you want to review uncommitted changes in a target repo, compare against the branch or commit you started from, for example:
```bash
cd /path/to/repo-you-want-to-review
bun --cwd /path/to/cline/sdk/apps/examples/security-review-bot dev main
```
## What it does
1. Reads a `git diff` against the specified ref (defaults to `HEAD~1`)
2. Starts a local `ClineCore` session with built-in tools enabled
3. Lets the model use ClineCore's `read_files` and `search_codebase` tools for surrounding file context
4. Adds two custom security review tools through `extraTools`:
- `add_security_finding` - records a structured security finding with severity, category, exploit scenario, remediation, and confidence
- `submit_security_review` - a completion tool that ends the run with a summary, overall risk rating, and merge-blocking decision
5. Prints findings grouped by severity (`critical`, `high`, `medium`, `low`, `info`)
## Finding schema
Each finding includes:
- `file` and `line` - where the issue appears
- `severity` - `critical`, `high`, `medium`, `low`, or `info`
- `category` - security category such as `injection`, `authorization`, `secrets`, `ssrf`, `xss`, or `cryptography`
- `cwe` and `owasp` - optional vulnerability taxonomy metadata
- `title` and `description` - concise summary and risk explanation
- `exploitScenario` - realistic abuse case
- `remediation` - concrete fix or mitigation
- `confidence` - `high`, `medium`, or `low`
## Concepts demonstrated
- Using `ClineCore.create()` and `cline.start()` for a local runtime session
- Enabling selected ClineCore built-in tools, including `read_files` and `search_codebase`
- Adding domain-specific custom tools with `extraTools`
- `lifecycle: { completesRun: true }` to make a tool end the agent loop
- Subscribing to `CoreSessionEvent` events with `cline.subscribe()`
- Rendering parsed `agent_event` text/tool events instead of printing serialized `chunk` payloads
- Cleaning up runtime resources with `cline.dispose()`
- Processing structured results after the run completes
## Notes
This is an AI-assisted review tool, not a replacement for SAST, dependency scanning, secret scanning, manual threat modeling, or human security review. Treat findings as review input and verify them before acting.
For a general-purpose reviewer, see [code-review-bot](../code-review-bot). For a simpler starting point, see [quickstart](../quickstart).
@@ -0,0 +1,22 @@
{
"name": "@cline/example-security-review-bot",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "bun run src/index.ts",
"build:sdk": "bun run --cwd ../../.. build:sdk",
"build": "tsc",
"start": "node dist/index.js"
},
"dependencies": {
"@cline/sdk": "workspace:*",
"zod": "^4.3.6"
},
"devDependencies": {
"typescript": "^5.9.3"
},
"engines": {
"node": ">=22"
}
}
@@ -0,0 +1,426 @@
import { execFileSync } from "node:child_process";
import {
type AgentEvent,
type AgentTool,
ClineCore,
createTool,
type ToolPolicy,
} from "@cline/sdk";
import { z } from "zod";
const SecurityFindingSchema = z.object({
file: z.string().describe("File path"),
line: z.number().describe("Line number (approximate is fine)"),
severity: z.enum(["critical", "high", "medium", "low", "info"]),
category: z
.enum([
"authentication",
"authorization",
"injection",
"secrets",
"cryptography",
"data_exposure",
"ssrf",
"xss",
"deserialization",
"path_traversal",
"dependency",
"logging_monitoring",
"configuration",
"other",
])
.describe("Security weakness category"),
cwe: z
.string()
.optional()
.describe("Relevant CWE identifier, such as CWE-89, if known"),
owasp: z.string().optional().describe("Relevant OWASP category, if known"),
title: z.string().describe("Short finding title"),
description: z.string().describe("What is vulnerable and why it matters"),
exploitScenario: z
.string()
.describe("A realistic abuse case or exploit path"),
remediation: z.string().describe("Concrete recommended fix or mitigation"),
confidence: z.enum(["high", "medium", "low"]),
});
const SecurityReviewResultSchema = z.object({
summary: z.string().describe("Brief overall security assessment"),
overallRisk: z.enum(["critical", "high", "medium", "low", "none"]),
blockMerge: z
.boolean()
.describe("Whether the changes should be blocked from merging"),
});
const findings: z.infer<typeof SecurityFindingSchema>[] = [];
let reviewResult: z.infer<typeof SecurityReviewResultSchema> | undefined;
const repoRoot = (() => {
try {
return execFileSync("git", ["rev-parse", "--show-toplevel"], {
encoding: "utf-8",
}).trim();
} catch {
console.error("Error: must be run from within a git repository.");
process.exit(1);
}
})();
const systemPrompt = `You are a senior application security engineer performing a focused security review of a git diff.
Your goal is to identify exploitable or defense-in-depth security issues introduced or modified by the diff. The repository root is ${repoRoot}. Use the read_files tool with absolute paths, or search_codebase, when you need surrounding code to determine whether a finding is real.
Focus on:
- Authentication and authorization bypasses
- Injection flaws, including SQL/NoSQL/command/template/LDAP injection
- Cross-site scripting and unsafe HTML/script rendering
- Server-side request forgery and unsafe URL fetching
- Path traversal and unsafe file operations
- Hardcoded secrets, tokens, private keys, and credential leakage
- Cryptographic misuse, weak randomness, insecure hashing, or missing integrity checks
- Sensitive data exposure in logs, errors, telemetry, APIs, or client bundles
- Unsafe deserialization or dynamic code execution
- Insecure dependency, environment, CORS, cookie, header, or cloud configuration changes
- Missing validation, rate limits, audit logging, or security checks around sensitive operations
Only report actionable findings that are supported by the diff or file context. Do not report generic best practices unless they materially reduce a realistic risk. Prefer fewer high-quality findings over many speculative ones.
Severity guidance:
- critical: likely remote code execution, auth bypass, mass data exposure, secret compromise, or trivially exploitable injection on sensitive data paths
- high: practical privilege escalation, significant data exposure, SSRF with meaningful impact, or exploitable stored XSS
- medium: plausible vulnerability requiring constraints, limited data exposure, missing authorization on lower-risk operations, or reflected XSS with user interaction
- low: hardening issue with limited exploitability
- info: noteworthy observation with minimal direct risk
For each real issue, call add_security_finding with clear evidence, exploit scenario, remediation, confidence, and CWE/OWASP metadata when applicable.
When you are done reviewing, call submit_security_review with a brief summary, an overall risk rating, and whether the changes should be blocked from merging.`;
const addSecurityFindingTool = createTool({
name: "add_security_finding",
description:
"Add an actionable security finding on a specific file and line.",
inputSchema: SecurityFindingSchema,
async execute(input) {
findings.push(input);
return {
success: true,
message: `Security finding added (${findings.length} total)`,
findingCount: findings.length,
};
},
});
const submitSecurityReviewTool = createTool({
name: "submit_security_review",
description: "Submit the completed security review with a risk summary.",
inputSchema: SecurityReviewResultSchema,
lifecycle: { completesRun: true },
async execute(input) {
reviewResult = input;
return {
success: true,
summary: input.summary,
overallRisk: input.overallRisk,
blockMerge: input.blockMerge,
findingCount: findings.length,
};
},
});
const securityReviewTools: AgentTool[] = [
addSecurityFindingTool as AgentTool,
submitSecurityReviewTool as AgentTool,
];
const securityReviewToolPolicies = {
"*": { autoApprove: true },
read_files: { autoApprove: true },
search_codebase: { autoApprove: true },
add_security_finding: { autoApprove: true },
submit_security_review: { autoApprove: true },
run_commands: { enabled: false },
fetch_web_content: { enabled: false },
editor: { enabled: false },
apply_patch: { enabled: false },
skills: { enabled: false },
ask_question: { enabled: false },
} satisfies Record<string, ToolPolicy>;
let reasoningOpen = false;
function usage() {
console.error("Usage: bun dev [git-ref] [--prompt <instructions>]");
console.error(" e.g. bun dev HEAD~3");
console.error(" e.g. bun dev main");
console.error(
' e.g. bun dev main --prompt "Focus on authorization bypasses and SSRF"',
);
console.error(
' e.g. bun dev main "Prioritize auth, secrets, and unsafe file access"',
);
}
function parseArgs(args: string[]): { ref: string; extraPrompt?: string } {
let ref = "HEAD~1";
let hasRef = false;
const promptParts: string[] = [];
const positionalPromptParts: string[] = [];
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === "--help" || arg === "-h") {
usage();
process.exit(0);
}
if (arg === "--prompt" || arg === "-p") {
const value = args[i + 1];
if (!value) {
console.error(`Missing value for ${arg}`);
usage();
process.exit(1);
}
promptParts.push(value);
i++;
continue;
}
if (arg.startsWith("--prompt=")) {
const value = arg.slice("--prompt=".length);
if (!value) {
console.error("Missing value for --prompt");
usage();
process.exit(1);
}
promptParts.push(value);
continue;
}
if (arg.startsWith("-")) {
console.error(`Unknown option: ${arg}`);
usage();
process.exit(1);
}
if (!hasRef) {
ref = arg;
hasRef = true;
continue;
}
positionalPromptParts.push(arg);
}
if (positionalPromptParts.length > 0) {
promptParts.push(positionalPromptParts.join(" "));
}
return {
ref,
extraPrompt: promptParts.length > 0 ? promptParts.join("\n\n") : undefined,
};
}
function closeReasoning() {
if (reasoningOpen) {
process.stdout.write("\n");
reasoningOpen = false;
}
}
function renderToolStart(
event: Extract<AgentEvent, { type: "content_start" }>,
) {
const toolName = event.toolName ?? "unknown_tool";
if (toolName === "add_security_finding") {
const parsed = SecurityFindingSchema.safeParse(event.input);
if (!parsed.success) {
console.log(`\n[tool] ${toolName}`);
return;
}
const input = parsed.data;
const icon =
input.severity === "critical"
? "X"
: input.severity === "high"
? "!"
: input.severity === "medium"
? "~"
: "i";
console.log(
`\n [${icon}] ${input.severity.toUpperCase()} ${input.file}:${input.line} - ${input.title}`,
);
return;
}
console.log(`\n[tool] ${toolName}`);
}
function renderAgentEvent(event: AgentEvent) {
switch (event.type) {
case "content_start":
if (event.contentType === "text" && event.text) {
closeReasoning();
process.stdout.write(event.text);
return;
}
if (event.contentType === "reasoning") {
if (event.redacted === true && !event.reasoning) {
console.log("\n[thinking redacted]");
return;
}
if (event.reasoning) {
if (!reasoningOpen) {
process.stdout.write("\n[thinking]\n");
reasoningOpen = true;
}
process.stdout.write(event.reasoning);
}
return;
}
if (event.contentType === "tool") {
closeReasoning();
renderToolStart(event);
return;
}
break;
case "content_end":
if (event.contentType === "reasoning") {
closeReasoning();
return;
}
if (event.contentType === "tool" && event.error) {
closeReasoning();
console.log(
`\n[tool failed] ${event.toolName ?? "unknown_tool"}: ${event.error}`,
);
}
break;
}
}
// Get the diff to review
const { ref, extraPrompt } = parseArgs(process.argv.slice(2));
if (ref.startsWith("-")) {
console.error(`Invalid ref: ${ref}`);
process.exit(1);
}
let diff: string;
try {
diff = execFileSync("git", ["diff", ref], {
encoding: "utf-8",
maxBuffer: 5 * 1024 * 1024,
});
} catch {
console.error(`Failed to get diff for ref: ${ref}`);
usage();
process.exit(1);
}
if (!diff.trim()) {
console.log("No diff found. Nothing to security review.");
process.exit(0);
}
console.log(
`Security reviewing diff against ${ref} (${diff.split("\n").length} lines)...\n`,
);
if (extraPrompt) {
console.log(`Additional review instructions: ${extraPrompt}\n`);
}
const cline = await ClineCore.create({
clientName: "security-review-bot",
backendMode: "local",
});
const unsubscribe = cline.subscribe((event) => {
switch (event.type) {
case "agent_event":
renderAgentEvent(event.payload.event);
break;
}
});
try {
const prompt = `Security review this git diff.${
extraPrompt ? `\n\nAdditional review instructions:\n${extraPrompt}` : ""
}\n\n\`\`\`diff\n${diff}\n\`\`\``;
const result = await cline.start({
source: "cli",
interactive: false,
prompt,
config: {
providerId: "cline",
modelId: "anthropic/claude-sonnet-4.6",
apiKey: process.env.CLINE_API_KEY,
cwd: repoRoot,
workspaceRoot: repoRoot,
mode: "act",
systemPrompt,
maxIterations: 25,
enableTools: true,
enableSpawnAgent: false,
enableAgentTeams: false,
disableMcpSettingsTools: true,
toolPolicies: securityReviewToolPolicies,
},
localRuntime: {
extraTools: securityReviewTools,
},
});
console.log("\n\n--- Security Review Complete ---\n");
if (reviewResult) {
console.log(`Summary: ${reviewResult.summary}`);
console.log(`Overall risk: ${reviewResult.overallRisk}`);
console.log(`Block merge: ${reviewResult.blockMerge ? "yes" : "no"}\n`);
}
if (findings.length === 0) {
console.log("No actionable security findings identified.");
} else {
const severityOrder = [
"critical",
"high",
"medium",
"low",
"info",
] as const;
for (const severity of severityOrder) {
const group = findings.filter((finding) => finding.severity === severity);
if (group.length === 0) {
continue;
}
console.log(`${severity.toUpperCase()}: ${group.length}`);
for (const finding of group) {
const metadata = [finding.cwe, finding.owasp]
.filter(Boolean)
.join(" | ");
console.log(
` ${finding.file}:${finding.line} - ${finding.title}${metadata ? ` (${metadata})` : ""}`,
);
console.log(` Category: ${finding.category}`);
console.log(` Confidence: ${finding.confidence}`);
console.log(` Risk: ${finding.description}`);
console.log(` Exploit: ${finding.exploitScenario}`);
console.log(` Fix: ${finding.remediation}`);
}
}
}
const usage = result.result?.usage;
console.log(
`\nSession: ${result.sessionId} | Status: ${result.result?.finishReason ?? "unknown"} | Iterations: ${result.result?.iterations ?? 0} | Tokens: ${usage?.outputTokens ?? 0} output`,
);
} finally {
unsubscribe();
await cline.dispose();
}
@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.apps.json",
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"outDir": "dist",
"strict": true,
"skipLibCheck": true,
"esModuleInterop": true
},
"include": ["src"]
}
+12
View File
@@ -197,6 +197,16 @@
"typescript": "^5.9.3",
},
},
"apps/examples/quickstart-clinecore": {
"name": "@cline/example-quickstart-clinecore",
"version": "0.0.0",
"dependencies": {
"@cline/sdk": "workspace:*",
},
"devDependencies": {
"typescript": "^5.9.3",
},
},
"apps/examples/security-review-bot": {
"name": "@cline/example-security-review-bot",
"version": "0.0.0",
@@ -635,6 +645,8 @@
"@cline/example-quickstart": ["@cline/example-quickstart@workspace:apps/examples/quickstart"],
"@cline/example-quickstart-clinecore": ["@cline/example-quickstart-clinecore@workspace:apps/examples/quickstart-clinecore"],
"@cline/example-security-review-bot": ["@cline/example-security-review-bot@workspace:apps/examples/security-review-bot"],
"@cline/llms": ["@cline/llms@workspace:packages/llms"],
@@ -1,7 +1,7 @@
import type {
GatewayModelRoute,
GatewayReasoningFormat,
GatewayProviderContext,
GatewayReasoningFormat,
GatewayStreamRequest,
} from "@cline/shared";
@@ -21,7 +21,9 @@ function normalizedFamily(context: GatewayProviderContext): string {
return normalizeRoutingValue(resolveModelFamily(context)) ?? "";
}
function normalizedModelId(request: Pick<GatewayStreamRequest, "modelId">): string {
function normalizedModelId(
request: Pick<GatewayStreamRequest, "modelId">,
): string {
return normalizeRoutingValue(request.modelId) ?? "";
}
@@ -38,7 +40,9 @@ function isClaudeLineageValue(value: string | undefined): boolean {
function isQwenLineageValue(value: string | undefined): boolean {
const normalized = normalizeRoutingValue(value);
return normalized ? /(^|[/:._-])qwen(?:$|[/:._-]|\d)/.test(normalized) : false;
return normalized
? /(^|[/:._-])qwen(?:$|[/:._-]|\d)/.test(normalized)
: false;
}
export function isAnthropicCompatibleModel(options: {