openhacker 0.1.0 → 0.1.1
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/package.json +1 -1
- package/src/cli.js +151 -28
- package/templates/agent/.env.example +0 -7
- package/templates/agent/README.md +1 -2
- package/templates/agent/agent/agent.ts +1 -5
- package/templates/agent/agent/channels/eve.ts +7 -0
- package/templates/agent/agent/instructions.md +7 -45
- package/templates/agent/app/globals.css +65 -197
- package/templates/agent/app/layout.tsx +2 -22
- package/templates/agent/app/page.tsx +80 -102
- package/templates/agent/package.json +2 -3
- package/templates/agent/agent/lib/auth.ts +0 -23
- package/templates/agent/agent/lib/github.ts +0 -74
- package/templates/agent/agent/lib/osv.ts +0 -152
- package/templates/agent/agent/lib/scan.ts +0 -153
- package/templates/agent/agent/lib/store.ts +0 -151
- package/templates/agent/agent/lib/types.ts +0 -63
- package/templates/agent/agent/schedules/daily_audit.ts +0 -20
- package/templates/agent/agent/tools/check_advisories.ts +0 -27
- package/templates/agent/agent/tools/list_targets.ts +0 -21
- package/templates/agent/agent/tools/read_repo_file.ts +0 -31
- package/templates/agent/agent/tools/report_finding.ts +0 -59
- package/templates/agent/agent/tools/run_dependency_scan.ts +0 -16
- package/templates/agent/app/_components/ui.tsx +0 -29
- package/templates/agent/app/actions.ts +0 -120
- package/templates/agent/app/api/scan/route.ts +0 -34
- package/templates/agent/app/login/page.tsx +0 -40
- package/templates/agent/app/settings/page.tsx +0 -92
- package/templates/agent/app/targets/[id]/page.tsx +0 -127
- package/templates/agent/proxy.ts +0 -21
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
1
2
|
import { cp, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
@@ -19,6 +20,36 @@ const EXCLUDE = new Set([
|
|
|
19
20
|
".turbo",
|
|
20
21
|
]);
|
|
21
22
|
|
|
23
|
+
// Written into scaffolded projects directly rather than shipped as a template
|
|
24
|
+
// file, because npm renames a packaged `.gitignore` to `.npmignore` on publish.
|
|
25
|
+
const GITIGNORE = `# dependencies
|
|
26
|
+
node_modules
|
|
27
|
+
|
|
28
|
+
# next.js
|
|
29
|
+
.next
|
|
30
|
+
next-env.d.ts
|
|
31
|
+
|
|
32
|
+
# eve build artifacts
|
|
33
|
+
.eve
|
|
34
|
+
.output
|
|
35
|
+
|
|
36
|
+
# vercel / turbo
|
|
37
|
+
.vercel
|
|
38
|
+
.turbo
|
|
39
|
+
|
|
40
|
+
# typescript
|
|
41
|
+
tsconfig.tsbuildinfo
|
|
42
|
+
|
|
43
|
+
# env files (keep the example)
|
|
44
|
+
.env
|
|
45
|
+
.env.*
|
|
46
|
+
!.env.example
|
|
47
|
+
|
|
48
|
+
# misc
|
|
49
|
+
.DS_Store
|
|
50
|
+
*.log
|
|
51
|
+
`;
|
|
52
|
+
|
|
22
53
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
23
54
|
|
|
24
55
|
async function exists(p) {
|
|
@@ -68,7 +99,73 @@ function shouldCopyTemplatePath(src, root) {
|
|
|
68
99
|
);
|
|
69
100
|
}
|
|
70
101
|
|
|
71
|
-
|
|
102
|
+
function runStep(command, args, cwd, { quiet = false } = {}) {
|
|
103
|
+
const result = spawnSync(command, args, {
|
|
104
|
+
cwd,
|
|
105
|
+
stdio: quiet ? "ignore" : "inherit",
|
|
106
|
+
shell: process.platform === "win32",
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return !result.error && result.status === 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function hasCommand(command) {
|
|
113
|
+
const probe = process.platform === "win32" ? "where" : "which";
|
|
114
|
+
const result = spawnSync(probe, [command], { stdio: "ignore", shell: process.platform === "win32" });
|
|
115
|
+
return !result.error && result.status === 0;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function isInsideGitRepo(cwd) {
|
|
119
|
+
const result = spawnSync("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
120
|
+
cwd,
|
|
121
|
+
stdio: "ignore",
|
|
122
|
+
shell: process.platform === "win32",
|
|
123
|
+
});
|
|
124
|
+
return !result.error && result.status === 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function installDependencies(dest) {
|
|
128
|
+
if (!hasCommand("pnpm")) {
|
|
129
|
+
console.log(`${MUTED}pnpm not found \u2014 skipping install. Run \`pnpm install\` manually.${NC}`);
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
console.log(`\n${MUTED}Installing dependencies with pnpm\u2026${NC}`);
|
|
134
|
+
// --ignore-workspace keeps the install self-contained: without it, pnpm walks
|
|
135
|
+
// up to a parent pnpm-workspace.yaml (e.g. when scaffolding inside a monorepo)
|
|
136
|
+
// and installs that workspace instead of the new project's node_modules.
|
|
137
|
+
const ok = runStep("pnpm", ["install", "--ignore-workspace"], dest);
|
|
138
|
+
if (!ok) {
|
|
139
|
+
console.log(`${RED}pnpm install failed.${NC} ${MUTED}You can re-run it inside the project.${NC}`);
|
|
140
|
+
}
|
|
141
|
+
return ok;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function initGitRepo(dest) {
|
|
145
|
+
if (!hasCommand("git")) {
|
|
146
|
+
console.log(`${MUTED}git not found \u2014 skipping repository init.${NC}`);
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (isInsideGitRepo(dest)) {
|
|
151
|
+
console.log(`${MUTED}Already inside a git repository \u2014 skipping git init.${NC}`);
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const initialized =
|
|
156
|
+
runStep("git", ["init"], dest, { quiet: true }) &&
|
|
157
|
+
runStep("git", ["add", "-A"], dest, { quiet: true }) &&
|
|
158
|
+
runStep("git", ["commit", "-m", "Initial commit from OpenHacker"], dest, { quiet: true });
|
|
159
|
+
|
|
160
|
+
if (initialized) {
|
|
161
|
+
console.log(`${MUTED}Initialized a git repository.${NC}`);
|
|
162
|
+
} else {
|
|
163
|
+
console.log(`${MUTED}Could not create the initial git commit \u2014 you can do it manually.${NC}`);
|
|
164
|
+
}
|
|
165
|
+
return initialized;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function init(targetArg, { skipInstall = false, skipGit = false } = {}) {
|
|
72
169
|
const template = await resolveTemplateDir();
|
|
73
170
|
if (!(await exists(path.join(template, "agent", "agent.ts")))) {
|
|
74
171
|
console.error(`${RED}Could not find the instance template at ${template}.${NC}`);
|
|
@@ -94,14 +191,25 @@ async function init(targetArg) {
|
|
|
94
191
|
pkg.private = true;
|
|
95
192
|
await writeFile(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
96
193
|
|
|
97
|
-
|
|
194
|
+
// Write before git init so the initial commit doesn't include node_modules/.env.
|
|
195
|
+
if (!(await exists(path.join(dest, ".gitignore")))) {
|
|
196
|
+
await writeFile(path.join(dest, ".gitignore"), GITIGNORE);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const installed = skipInstall ? false : await installDependencies(dest);
|
|
200
|
+
if (!skipGit) {
|
|
201
|
+
await initGitRepo(dest);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
console.log(`\n${ORANGE}\u2713${NC} OpenHacker instance ready.\n`);
|
|
98
205
|
console.log("Next steps:\n");
|
|
99
206
|
console.log(` cd ${path.relative(process.cwd(), dest) || "."}`);
|
|
100
|
-
|
|
207
|
+
if (skipInstall || !installed) {
|
|
208
|
+
console.log(" pnpm install");
|
|
209
|
+
}
|
|
101
210
|
console.log(" pnpm dev # run locally\n");
|
|
102
211
|
console.log(`${MUTED}Deploy: push to a git repo and import it into Vercel (deploys as one project).`);
|
|
103
|
-
console.log(
|
|
104
|
-
console.log(`OPENHACKER_ADMIN_PASSWORD to protect the dashboard. See README.md.${NC}\n`);
|
|
212
|
+
console.log(`Add a Vercel KV / Upstash Redis integration to persist findings. See README.md.${NC}\n`);
|
|
105
213
|
}
|
|
106
214
|
|
|
107
215
|
function usage() {
|
|
@@ -111,6 +219,9 @@ function usage() {
|
|
|
111
219
|
console.log(" openhacker init [dir] Same as above");
|
|
112
220
|
console.log(" openhacker --help Show help");
|
|
113
221
|
console.log(" openhacker --version Show version\n");
|
|
222
|
+
console.log("Options:");
|
|
223
|
+
console.log(" --skip-install Don't run pnpm install");
|
|
224
|
+
console.log(" --skip-git Don't create a git repository\n");
|
|
114
225
|
}
|
|
115
226
|
|
|
116
227
|
async function version() {
|
|
@@ -119,7 +230,36 @@ async function version() {
|
|
|
119
230
|
}
|
|
120
231
|
|
|
121
232
|
export async function run(args = process.argv.slice(2)) {
|
|
122
|
-
const
|
|
233
|
+
const options = { skipInstall: false, skipGit: false };
|
|
234
|
+
const positionals = [];
|
|
235
|
+
|
|
236
|
+
for (const arg of args) {
|
|
237
|
+
switch (arg) {
|
|
238
|
+
case "--skip-install":
|
|
239
|
+
options.skipInstall = true;
|
|
240
|
+
break;
|
|
241
|
+
case "--skip-git":
|
|
242
|
+
options.skipGit = true;
|
|
243
|
+
break;
|
|
244
|
+
case "-h":
|
|
245
|
+
case "--help":
|
|
246
|
+
usage();
|
|
247
|
+
return;
|
|
248
|
+
case "-v":
|
|
249
|
+
case "--version":
|
|
250
|
+
await version();
|
|
251
|
+
return;
|
|
252
|
+
default:
|
|
253
|
+
if (arg.startsWith("-")) {
|
|
254
|
+
console.error(`${RED}Unknown option: ${arg}${NC}\n`);
|
|
255
|
+
usage();
|
|
256
|
+
process.exit(1);
|
|
257
|
+
}
|
|
258
|
+
positionals.push(arg);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const [command, target, ...rest] = positionals;
|
|
123
263
|
|
|
124
264
|
if (rest.length > 0) {
|
|
125
265
|
console.error(`${RED}Too many arguments.${NC}\n`);
|
|
@@ -127,27 +267,10 @@ export async function run(args = process.argv.slice(2)) {
|
|
|
127
267
|
process.exit(1);
|
|
128
268
|
}
|
|
129
269
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
break;
|
|
134
|
-
case "init":
|
|
135
|
-
await init(target);
|
|
136
|
-
break;
|
|
137
|
-
case "-h":
|
|
138
|
-
case "--help":
|
|
139
|
-
usage();
|
|
140
|
-
break;
|
|
141
|
-
case "-v":
|
|
142
|
-
case "--version":
|
|
143
|
-
await version();
|
|
144
|
-
break;
|
|
145
|
-
default:
|
|
146
|
-
if (command.startsWith("-")) {
|
|
147
|
-
console.error(`${RED}Unknown option: ${command}${NC}\n`);
|
|
148
|
-
usage();
|
|
149
|
-
process.exit(1);
|
|
150
|
-
}
|
|
151
|
-
await init(command);
|
|
270
|
+
if (command === "init") {
|
|
271
|
+
await init(target, options);
|
|
272
|
+
return;
|
|
152
273
|
}
|
|
274
|
+
|
|
275
|
+
await init(command, options);
|
|
153
276
|
}
|
|
@@ -3,13 +3,6 @@
|
|
|
3
3
|
# For LOCAL runs of the eve agent, provide a gateway key:
|
|
4
4
|
AI_GATEWAY_API_KEY=
|
|
5
5
|
|
|
6
|
-
# Optional: override the agent's deep-analysis model (defaults to anthropic/claude-sonnet-4.6)
|
|
7
|
-
OPENHACKER_MODEL=
|
|
8
|
-
|
|
9
|
-
# --- Dashboard auth (recommended) ---
|
|
10
|
-
# When set, the dashboard requires this password to sign in. Unset = open dashboard.
|
|
11
|
-
OPENHACKER_ADMIN_PASSWORD=
|
|
12
|
-
|
|
13
6
|
# --- Persistent storage ---
|
|
14
7
|
# Add a Vercel KV / Upstash Redis integration. Without these, an in-memory store is
|
|
15
8
|
# used and data does NOT persist across restarts/deploys.
|
|
@@ -17,8 +17,7 @@ Your self-hosted OpenHacker security agent — a Next.js dashboard with an embed
|
|
|
17
17
|
2. Import it into Vercel (it deploys as one project — UI + agent + cron).
|
|
18
18
|
3. Add a **KV / Upstash Redis** integration from the Vercel Marketplace so findings persist
|
|
19
19
|
(`KV_REST_API_URL` / `KV_REST_API_TOKEN` are injected automatically).
|
|
20
|
-
4.
|
|
21
|
-
5. Open the deployment URL, sign in, and add a target repository.
|
|
20
|
+
4. Open the deployment URL and add a target repository.
|
|
22
21
|
|
|
23
22
|
Inference runs through the Vercel AI Gateway and authenticates automatically via Vercel
|
|
24
23
|
OIDC — no model API key required in production.
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { defineAgent } from "eve";
|
|
2
2
|
|
|
3
3
|
export default defineAgent({
|
|
4
|
-
|
|
5
|
-
// injected VERCEL_OIDC_TOKEN automatically; locally it uses AI_GATEWAY_API_KEY.
|
|
6
|
-
// The model picker in the dashboard writes OPENHACKER_MODEL.
|
|
7
|
-
model: process.env.OPENHACKER_MODEL ?? "anthropic/claude-sonnet-4.6",
|
|
8
|
-
reasoning: "medium",
|
|
4
|
+
model: "anthropic/claude-haiku-4.5",
|
|
9
5
|
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { eveChannel } from "eve/channels/eve";
|
|
2
|
+
import { none } from "eve/channels/auth";
|
|
3
|
+
|
|
4
|
+
// Public demo: anyone can call the agent from the browser. Add real auth
|
|
5
|
+
// (e.g. vercelOidc/localDev or your own session check) before exposing
|
|
6
|
+
// anything sensitive.
|
|
7
|
+
export default eveChannel({ auth: [none()] });
|
|
@@ -1,49 +1,11 @@
|
|
|
1
1
|
# OpenHacker
|
|
2
2
|
|
|
3
|
-
You are OpenHacker, an
|
|
4
|
-
real, exploitable vulnerabilities in a codebase and its dependencies, prove them,
|
|
5
|
-
and propose fixes. You operate continuously and on demand.
|
|
3
|
+
You are OpenHacker, an application security agent.
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
The user gives you a GitHub repository (as `owner/name` or a github.com URL). Analyze it for security vulnerabilities and report your findings.
|
|
8
6
|
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
input) to the dangerous sink. Capture that data flow as your evidence. Set the
|
|
15
|
-
finding's `proof.status` honestly: `proven`, `likely`, or `unconfirmed`.
|
|
16
|
-
- **Be conservative with dependency CVEs.** A package having a CVE does not mean this
|
|
17
|
-
project is exploitable. Use `check_advisories` to confirm the *installed version* is
|
|
18
|
-
in the affected range, then assess whether the vulnerable code path is plausibly
|
|
19
|
-
reachable before reporting.
|
|
20
|
-
|
|
21
|
-
## Tools
|
|
22
|
-
|
|
23
|
-
- `list_targets` — see the repositories configured in this instance.
|
|
24
|
-
- `run_dependency_scan` — deterministically check a target's dependencies against OSV
|
|
25
|
-
and persist findings. Run this first for dependency coverage.
|
|
26
|
-
- `read_repo_file` — read files / list directories in a target's repo for code review.
|
|
27
|
-
- `check_advisories` — look up a single package in OSV.
|
|
28
|
-
- `report_finding` — persist a confirmed code-level vulnerability.
|
|
29
|
-
|
|
30
|
-
## Workflow
|
|
31
|
-
|
|
32
|
-
Given a target id:
|
|
33
|
-
|
|
34
|
-
1. Call `run_dependency_scan` to record known-vulnerable dependencies.
|
|
35
|
-
2. Use `read_repo_file` (list then read) to inspect the source layout, framework, and
|
|
36
|
-
trust boundaries (route handlers, server actions, request/query/header/env inputs).
|
|
37
|
-
3. Hunt for high-impact issue classes: broken authorization, injection (SQL/command/
|
|
38
|
-
template), SSRF, secrets in code, unsafe deserialization, and XSS (including
|
|
39
|
-
`dangerouslySetInnerHTML`).
|
|
40
|
-
4. For each confirmed code issue, call `report_finding` (with `targetId`) including an
|
|
41
|
-
accurate severity, location, data-flow evidence, and a concrete remediation.
|
|
42
|
-
5. Summarize what you checked and what you found.
|
|
43
|
-
|
|
44
|
-
## Severity
|
|
45
|
-
|
|
46
|
-
Use CVSS-style judgment: `critical` for unauthenticated RCE / auth bypass / data
|
|
47
|
-
exfiltration; `high` for authenticated high-impact issues; `medium` for issues
|
|
48
|
-
needing preconditions; `low`/`info` for hardening. Prefer under-reporting to
|
|
49
|
-
crying wolf.
|
|
7
|
+
- Briefly narrate what you are checking as you go (dependencies, auth, input handling, secrets, etc.).
|
|
8
|
+
- Use the shell to clone and explore the repo. Use `ls`/`find` to list directories; only use `read_file` on actual file paths, never on a directory.
|
|
9
|
+
- Call out concrete risks with severity and, where possible, how to fix them.
|
|
10
|
+
- If you cannot access the repository, say so plainly.
|
|
11
|
+
- Keep the final summary concise and skimmable.
|
|
@@ -8,11 +8,6 @@
|
|
|
8
8
|
--accent: #ffffff;
|
|
9
9
|
--accent-dim: #333333;
|
|
10
10
|
--crit: #ffffff;
|
|
11
|
-
--high: #cfcfcf;
|
|
12
|
-
--med: #9a9a9a;
|
|
13
|
-
--low: #6f6f6f;
|
|
14
|
-
--info: #555555;
|
|
15
|
-
--ok: #f5f5f5;
|
|
16
11
|
}
|
|
17
12
|
|
|
18
13
|
* {
|
|
@@ -27,254 +22,127 @@ body {
|
|
|
27
22
|
color: var(--text);
|
|
28
23
|
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
29
24
|
font-size: 14px;
|
|
30
|
-
line-height: 1.
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
a {
|
|
34
|
-
color: var(--accent);
|
|
35
|
-
text-decoration: none;
|
|
36
|
-
}
|
|
37
|
-
a:hover {
|
|
38
|
-
text-decoration: underline;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
.nav {
|
|
42
|
-
display: flex;
|
|
43
|
-
align-items: center;
|
|
44
|
-
gap: 20px;
|
|
45
|
-
padding: 14px 24px;
|
|
46
|
-
border-bottom: 1px solid var(--border);
|
|
47
|
-
background: var(--panel);
|
|
48
|
-
}
|
|
49
|
-
.nav .brand {
|
|
50
|
-
font-weight: 700;
|
|
51
|
-
letter-spacing: 0.5px;
|
|
52
|
-
color: var(--text);
|
|
53
|
-
}
|
|
54
|
-
.nav .brand span {
|
|
55
|
-
color: var(--accent);
|
|
56
|
-
}
|
|
57
|
-
.nav .spacer {
|
|
58
|
-
flex: 1;
|
|
59
|
-
}
|
|
60
|
-
.nav a {
|
|
61
|
-
color: var(--muted);
|
|
62
|
-
}
|
|
63
|
-
.nav a:hover {
|
|
64
|
-
color: var(--text);
|
|
65
|
-
text-decoration: none;
|
|
25
|
+
line-height: 1.6;
|
|
66
26
|
}
|
|
67
27
|
|
|
68
28
|
.container {
|
|
69
|
-
max-width:
|
|
29
|
+
max-width: 720px;
|
|
70
30
|
margin: 0 auto;
|
|
71
|
-
padding:
|
|
31
|
+
padding: 64px 24px 96px;
|
|
72
32
|
}
|
|
73
33
|
|
|
74
34
|
h1 {
|
|
75
|
-
font-size:
|
|
35
|
+
font-size: 22px;
|
|
36
|
+
font-weight: 700;
|
|
37
|
+
letter-spacing: 0.5px;
|
|
76
38
|
margin: 0 0 4px;
|
|
77
39
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
margin: 28px 0 12px;
|
|
81
|
-
color: var(--muted);
|
|
82
|
-
text-transform: uppercase;
|
|
83
|
-
letter-spacing: 1px;
|
|
40
|
+
h1 span {
|
|
41
|
+
color: var(--accent);
|
|
84
42
|
}
|
|
85
43
|
.sub {
|
|
86
44
|
color: var(--muted);
|
|
87
|
-
margin: 0 0
|
|
45
|
+
margin: 0 0 28px;
|
|
88
46
|
}
|
|
89
47
|
|
|
90
|
-
.
|
|
91
|
-
background: var(--panel);
|
|
92
|
-
border: 1px solid var(--border);
|
|
93
|
-
border-radius: 10px;
|
|
94
|
-
padding: 18px;
|
|
95
|
-
margin-bottom: 16px;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
.card {
|
|
99
|
-
background: var(--panel);
|
|
100
|
-
border: 1px solid var(--border);
|
|
101
|
-
border-radius: 10px;
|
|
102
|
-
padding: 16px 18px;
|
|
103
|
-
margin-bottom: 12px;
|
|
48
|
+
.ask {
|
|
104
49
|
display: flex;
|
|
105
|
-
|
|
106
|
-
gap: 16px;
|
|
50
|
+
gap: 8px;
|
|
107
51
|
}
|
|
108
|
-
.
|
|
52
|
+
.ask input {
|
|
109
53
|
flex: 1;
|
|
110
|
-
min-width: 0;
|
|
111
|
-
}
|
|
112
|
-
.card .repo {
|
|
113
|
-
font-weight: 600;
|
|
114
|
-
}
|
|
115
|
-
.card .meta {
|
|
116
|
-
color: var(--muted);
|
|
117
|
-
font-size: 12px;
|
|
118
|
-
margin-top: 2px;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
label {
|
|
122
|
-
display: block;
|
|
123
|
-
font-size: 12px;
|
|
124
|
-
color: var(--muted);
|
|
125
|
-
margin-bottom: 6px;
|
|
126
|
-
}
|
|
127
|
-
input[type="text"],
|
|
128
|
-
input[type="password"],
|
|
129
|
-
select {
|
|
130
|
-
width: 100%;
|
|
131
54
|
background: var(--panel-2);
|
|
132
55
|
border: 1px solid var(--border);
|
|
133
56
|
color: var(--text);
|
|
134
57
|
border-radius: 8px;
|
|
135
|
-
padding:
|
|
58
|
+
padding: 11px 13px;
|
|
136
59
|
font: inherit;
|
|
137
60
|
}
|
|
138
|
-
input:focus
|
|
139
|
-
select:focus {
|
|
61
|
+
.ask input:focus {
|
|
140
62
|
outline: none;
|
|
141
63
|
border-color: var(--accent);
|
|
142
64
|
}
|
|
143
|
-
.
|
|
144
|
-
display: flex;
|
|
145
|
-
gap: 12px;
|
|
146
|
-
flex-wrap: wrap;
|
|
147
|
-
margin-bottom: 12px;
|
|
148
|
-
}
|
|
149
|
-
.row > div {
|
|
150
|
-
flex: 1;
|
|
151
|
-
min-width: 160px;
|
|
152
|
-
}
|
|
153
|
-
.check {
|
|
154
|
-
display: flex;
|
|
155
|
-
align-items: center;
|
|
156
|
-
gap: 8px;
|
|
157
|
-
}
|
|
158
|
-
.check label {
|
|
159
|
-
margin: 0;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
button,
|
|
163
|
-
.btn {
|
|
65
|
+
.ask button {
|
|
164
66
|
background: var(--accent);
|
|
165
67
|
color: #000000;
|
|
166
68
|
border: none;
|
|
167
69
|
border-radius: 8px;
|
|
168
|
-
padding:
|
|
70
|
+
padding: 11px 18px;
|
|
169
71
|
font: inherit;
|
|
170
72
|
font-weight: 600;
|
|
171
73
|
cursor: pointer;
|
|
172
74
|
}
|
|
173
|
-
button:hover {
|
|
75
|
+
.ask button:hover:not(:disabled) {
|
|
174
76
|
filter: brightness(1.08);
|
|
175
77
|
}
|
|
176
|
-
.
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
border: 1px solid var(--border);
|
|
180
|
-
}
|
|
181
|
-
.btn-ghost:hover {
|
|
182
|
-
color: var(--text);
|
|
183
|
-
}
|
|
184
|
-
.btn-danger {
|
|
185
|
-
background: transparent;
|
|
186
|
-
color: var(--crit);
|
|
187
|
-
border: 1px solid var(--accent-dim);
|
|
78
|
+
.ask button:disabled {
|
|
79
|
+
opacity: 0.5;
|
|
80
|
+
cursor: not-allowed;
|
|
188
81
|
}
|
|
189
82
|
|
|
190
|
-
.
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
border-radius: 20px;
|
|
194
|
-
font-size: 11px;
|
|
195
|
-
font-weight: 700;
|
|
196
|
-
text-transform: uppercase;
|
|
197
|
-
letter-spacing: 0.5px;
|
|
83
|
+
.reply {
|
|
84
|
+
margin-top: 28px;
|
|
85
|
+
background: var(--panel);
|
|
198
86
|
border: 1px solid var(--border);
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
.sev-critical {
|
|
203
|
-
background: #ffffff;
|
|
204
|
-
color: #000000;
|
|
205
|
-
border-color: #ffffff;
|
|
206
|
-
}
|
|
207
|
-
.sev-high {
|
|
208
|
-
color: #ffffff;
|
|
209
|
-
border-color: #cfcfcf;
|
|
210
|
-
background: rgba(255, 255, 255, 0.1);
|
|
87
|
+
border-radius: 10px;
|
|
88
|
+
padding: 18px 20px;
|
|
211
89
|
}
|
|
212
|
-
.
|
|
213
|
-
|
|
214
|
-
|
|
90
|
+
.reply .text {
|
|
91
|
+
white-space: pre-wrap;
|
|
92
|
+
margin: 0 0 12px;
|
|
215
93
|
}
|
|
216
|
-
.
|
|
217
|
-
|
|
218
|
-
border-color: #3a3a3a;
|
|
94
|
+
.reply .text:last-child {
|
|
95
|
+
margin-bottom: 0;
|
|
219
96
|
}
|
|
220
|
-
.
|
|
221
|
-
|
|
222
|
-
|
|
97
|
+
.reply .reasoning {
|
|
98
|
+
white-space: pre-wrap;
|
|
99
|
+
color: var(--muted);
|
|
100
|
+
font-size: 13px;
|
|
101
|
+
margin: 0 0 12px;
|
|
102
|
+
padding-left: 12px;
|
|
103
|
+
border-left: 2px solid var(--border);
|
|
223
104
|
}
|
|
224
105
|
|
|
225
|
-
.
|
|
106
|
+
.tool {
|
|
226
107
|
display: flex;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
border-collapse: collapse;
|
|
108
|
+
align-items: center;
|
|
109
|
+
gap: 10px;
|
|
110
|
+
font-size: 12.5px;
|
|
111
|
+
color: var(--muted);
|
|
112
|
+
padding: 4px 0;
|
|
233
113
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
text-align: left;
|
|
237
|
-
padding: 10px 12px;
|
|
238
|
-
border-bottom: 1px solid var(--border);
|
|
239
|
-
vertical-align: top;
|
|
240
|
-
font-size: 13px;
|
|
114
|
+
.tool-name {
|
|
115
|
+
color: var(--text);
|
|
241
116
|
}
|
|
242
|
-
|
|
243
|
-
color: var(--muted);
|
|
117
|
+
.tool-state {
|
|
244
118
|
font-size: 11px;
|
|
245
119
|
text-transform: uppercase;
|
|
246
120
|
letter-spacing: 0.5px;
|
|
121
|
+
border: 1px solid var(--border);
|
|
122
|
+
border-radius: 20px;
|
|
123
|
+
padding: 0 8px;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.cursor {
|
|
127
|
+
display: inline-block;
|
|
128
|
+
width: 8px;
|
|
129
|
+
height: 1em;
|
|
130
|
+
background: var(--accent);
|
|
131
|
+
vertical-align: text-bottom;
|
|
132
|
+
animation: blink 1s steps(2) infinite;
|
|
133
|
+
}
|
|
134
|
+
@keyframes blink {
|
|
135
|
+
50% {
|
|
136
|
+
opacity: 0;
|
|
137
|
+
}
|
|
247
138
|
}
|
|
248
139
|
|
|
249
140
|
.banner {
|
|
141
|
+
margin-top: 20px;
|
|
250
142
|
border: 1px solid var(--border);
|
|
251
143
|
background: rgba(255, 255, 255, 0.04);
|
|
252
|
-
color: var(--
|
|
144
|
+
color: var(--crit);
|
|
253
145
|
border-radius: 8px;
|
|
254
146
|
padding: 10px 14px;
|
|
255
|
-
margin-bottom: 18px;
|
|
256
147
|
font-size: 13px;
|
|
257
148
|
}
|
|
258
|
-
.empty {
|
|
259
|
-
color: var(--muted);
|
|
260
|
-
padding: 24px;
|
|
261
|
-
text-align: center;
|
|
262
|
-
border: 1px dashed var(--border);
|
|
263
|
-
border-radius: 10px;
|
|
264
|
-
}
|
|
265
|
-
.inline {
|
|
266
|
-
display: inline;
|
|
267
|
-
}
|
|
268
|
-
.actions {
|
|
269
|
-
display: flex;
|
|
270
|
-
gap: 8px;
|
|
271
|
-
align-items: center;
|
|
272
|
-
}
|
|
273
|
-
.mono-sm {
|
|
274
|
-
font-size: 12px;
|
|
275
|
-
color: var(--muted);
|
|
276
|
-
}
|
|
277
|
-
.login-wrap {
|
|
278
|
-
max-width: 360px;
|
|
279
|
-
margin: 12vh auto 0;
|
|
280
|
-
}
|