@hirakoxx/cli 0.3.0 → 0.4.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 (3) hide show
  1. package/README.md +14 -4
  2. package/dist/index.js +596 -67
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -37,10 +37,11 @@ Temperature controls how random the model's output is: `0` = deterministic and f
37
37
  - **Message queue** — send another message while the agent is working; it is queued and sent automatically when the turn finishes (with an `N queued` indicator).
38
38
  - **Accent themes** (`/theme`, default `#fab283`) plus **compact mode** (`/compact`) for small screens.
39
39
  - **Agent loop** — thinking → tool call → approval → execution → repeat.
40
- - **8 tools** — `read_file`, `list_dir`, `glob`, `edit_file`, `write_file`, `run_bash`, `http_request`, `port_scan` (with service, version-banner and TLS certificate fingerprinting).
41
- - **Build mode** — the default agent works like a staff engineer on safety-critical software: explicit design, smallest correct diff, and real verification instead of promises.
42
- - **Pentest mode** — a full attacker playbook: rules of engagement, seven phases from recon to reporting, concrete tooling and payloads.
40
+ - **9 tools** — `read_file`, `list_dir`, `glob`, `edit_file`, `write_file`, `run_bash`, `http_request`, `port_scan` (service, version-banner and TLS certificate fingerprinting) and `cve_lookup` (NVD CVE matching by product and version).
41
+ - **Build mode** — the default agent works like a staff engineer on safety-critical software: explicit design, smallest correct diff, real verification, and a whole-tree codebase audit procedure.
42
+ - **Pentest mode** — a full attacker playbook: rules of engagement, seven phases from recon to reporting, SQL injection and injection tooling, CVE exploitation intelligence, and a complete toolkit.
43
43
  - **Security review mode** — a read-only source audit that traces untrusted input to dangerous sinks and reports `file:line` with a fix. See [Agents](#agents).
44
+ - **Strict mode isolation** — one mode per session. Each agent declares its own `MODE:`, the base prompt defers to it, and a mode asked to do another mode's job hands off with `/agent` instead of drifting.
44
45
  - **Approval system** — dangerous actions ask for `y`/`n` confirmation.
45
46
  - **Multi-session** — session sidebar, history persisted to disk.
46
47
  - **Single-file bundle** — `bun run build:npm` produces one self-contained Node file with **zero dependencies**; `bun run build` produces a native binary.
@@ -213,19 +214,28 @@ The model must be **vision-capable**. If it isn't, HiraxCode refuses to send and
213
214
  | `run_bash` | Run a shell command (PowerShell on Windows, bash/zsh on macOS/Linux) | **yes** |
214
215
  | `http_request` | Send an HTTP/HTTPS request and inspect status, headers, body (web probing) | no |
215
216
  | `port_scan` | TCP-connect scan with service label, version banner and TLS certificate fingerprinting | **yes** |
217
+ | `cve_lookup` | Look up published CVEs for a product/version in the NIST NVD, with CVSS vectors and references | no |
216
218
 
217
219
  ## Agents
218
220
 
219
221
  Four built-in agents, selectable with `/agent <id>`, `/pentest`, or the agent picker:
220
222
 
221
- - **Build** (`build`, default) — works like a staff engineer on safety-critical software. It establishes the real requirement first, makes behaviour explicit (empty/missing input, null, overflow, unicode, timezones, concurrency, error paths), keeps the **smallest correct diff**, fails loudly instead of degrading silently, and **verifies** with typecheck/build/tests before reporting back. It never claims verification it did not perform — every answer ends with what changed, what was proven, and what is still unverified.
223
+ - **Build** (`build`, default) — works like a staff engineer on safety-critical software. It establishes the real requirement first, makes behaviour explicit (empty/missing input, null, overflow, unicode, timezones, concurrency, error paths), keeps the **smallest correct diff**, fails loudly instead of degrading silently, and **verifies** with typecheck/build/tests before reporting back. It never claims verification it did not perform — every answer ends with what changed, what was proven, and what is still unverified. When you ask it to review or "check everything", it **walks the whole tree instead of sampling**: map the modules and entry points, read every source file, work a fixed checklist (correctness, error handling, resource lifecycle, concurrency, input validation, dead code, duplication, docs vs behaviour, test quality), report `severity · file:line · why it matters · fix`, then fix in small verified batches with a regression test per bug.
222
224
  - **Plan** (`plan`) — read-only exploration. Reads, lists and globs, with no edit/write/shell tools, so it can investigate without touching anything.
223
225
  - **Pentest** (`pentest`) — for **authorised** engagements, a seven-phase operator playbook: scope & evidence folder → passive recon (DNS/AXFR, certificate transparency, TLS metadata, exposed files, stack fingerprinting) → active scan with version → CVE mapping → per-service enumeration (web routes and parameters, GraphQL, SMB/RPC, SSH/FTP, data stores, Docker/kubelet/etcd, SNMP/NFS) → vulnerability classes ordered by real-world yield (broken access control/IDOR first, then authn & JWT, injection, file handling, SSRF/smuggling/cache poisoning, client-side, deserialisation, secrets, business logic) → exploitation and privilege escalation (runnable PoCs, Linux and Windows privesc, pivoting only when in scope, artefact cleanup) → `findings.md` with severity, exact reproduction, evidence, impact, root cause and copy-pasteable remediation.
224
226
  It is **non-destructive by default** — no DoS, no destructive writes, no bulk exfiltration — and proves impact with the minimum action. Findings stay labelled `confirmed`, `likely` or `unverified`.
227
+ Reconnaissance is treated as a starting point, not a result: the agent attacks with every class of technique in whatever order the target rewards — enumeration, **SQL injection** (boolean, error-based, time-based and UNION, plus NoSQL/LDAP/XPath/ORM/second-order variants, hand-verified before any `sqlmap -r` run), authentication and authorisation attacks, credential attacks, misconfiguration, exploit development and post-exploitation. For **CVE work** it calls `cve_lookup` with the product and version observed by `port_scan`, which queries the NIST NVD, filters by the real CPE version ranges, and returns CVSS severity, score and vector, so a banner becomes a checkable vulnerability instead of a guess.
225
228
  - **Security** (`security`) — a **read-only** source audit: maps entry points and trust boundaries, traces untrusted input to dangerous sinks (SQL, shell, filesystem, template, deserialiser, HTTP client, logger) and reports `file:line` with the exploitable path, a concrete fix, and a prioritised fix order. No write/edit tools.
226
229
 
227
230
  The security agents answer directly — they assume an authorised engagement and won't bury you in caveats. Point them at targets you own or are permitted to test: `port_scan` and `run_bash` require approval (unless you turn on `/yolo`), `http_request` is read-only and runs immediately.
228
231
 
232
+ **Modes do not mix.** Exactly one mode is active per session and it declares itself at the top of the system prompt (`MODE: BUILD`, `MODE: PENTEST`, `MODE: SECURITY REVIEW`, `MODE: PLAN`, `MODE: GENERAL`). The base prompt defers to that declaration, so:
233
+ - Build and Plan stay on software: they will not drift into payloads or exploitation.
234
+ - Pentest and Security stay on the engagement: they will not turn into product feature work.
235
+ - If you ask a mode for another mode's job, it names the right agent and tells you to switch with `/agent` instead of quietly changing role.
236
+
237
+ Switching with `/agent <id>`, `/pentest` or `/plan` starts a fresh system prompt, so the previous mode's instructions are gone rather than stacked.
238
+
229
239
  Example:
230
240
 
231
241
  ```
package/dist/index.js CHANGED
@@ -46226,7 +46226,9 @@ function saveSettings(settings) {
46226
46226
  encoding: "utf-8",
46227
46227
  mode: 384
46228
46228
  });
46229
- } catch {}
46229
+ } catch (e) {
46230
+ logger.warn(`could not save settings to ${configPath()}: ${e.message}`);
46231
+ }
46230
46232
  }
46231
46233
  function splitApiKeys(raw) {
46232
46234
  if (!raw)
@@ -46272,9 +46274,15 @@ function clampTemperature(n) {
46272
46274
 
46273
46275
  // src/core/agents.ts
46274
46276
  var BUILD_SYSTEM = [
46275
- "You are in BUILD mode — the primary implementation agent. Operate as a staff-level",
46276
- "engineer who has shipped safety-critical software: correctness is non-negotiable and",
46277
- "every line you write must survive review by a hostile reviewer.",
46277
+ "MODE: BUILD — software development. You hold this mode for the whole session.",
46278
+ "This is a coding mode: everything you do serves building, fixing, reviewing or shipping",
46279
+ "software. Do not drift into offensive-security work here. If the user wants to attack a",
46280
+ "system or hunt it for weaknesses, say so and tell them to switch to the pentest or",
46281
+ "security agent with /agent — then stop, do not attempt it in this mode.",
46282
+ "",
46283
+ "You are the primary implementation agent. Operate as a staff-level engineer who has",
46284
+ "shipped safety-critical software: correctness is non-negotiable and every line you",
46285
+ "write must survive review by a hostile reviewer.",
46278
46286
  "",
46279
46287
  "MINDSET",
46280
46288
  "- Establish the real requirement before writing code. If it is ambiguous, state the",
@@ -46325,21 +46333,75 @@ var BUILD_SYSTEM = [
46325
46333
  "- Open with what you are about to change and why, in one or two lines.",
46326
46334
  "- Close with: what changed, which files, what you verified, and what remains.",
46327
46335
  "- Flag real risks and the trade-offs you consciously accepted. Do not pad the answer.",
46328
- "- Assume a competent professional reader. No hedging, no filler, no moralising."
46336
+ "- Assume a competent professional reader. No hedging, no filler, no moralising.",
46337
+ "",
46338
+ "CODING CRAFT — beyond 'it works'",
46339
+ "- Prefer the standard library and the patterns already in the repo over new abstractions",
46340
+ " or new dependencies. Every dependency is a permanent liability.",
46341
+ "- Functions do one thing and have honest names. Avoid boolean-flag parameters that switch",
46342
+ " behaviour; pass the intent instead.",
46343
+ "- Make illegal states unrepresentable: types and invariants first, runtime validation at",
46344
+ " the boundary second, defensive re-checks of impossible states last.",
46345
+ "- Keep the core pure and the effects at the edge — that is what makes it testable.",
46346
+ "- Test the failure paths, not just the happy path. Tests must be deterministic: no wall",
46347
+ " clock, no real network, no fixed ports, no ordering assumptions.",
46348
+ "- Handle an error where you have the context to act on it; otherwise propagate it with",
46349
+ " enough information to be actionable. Never swallow one.",
46350
+ "- Delete dead code, commented-out blocks and unused parameters instead of preserving them.",
46351
+ "- When you touch a file, leave it consistent: same naming, same error style, same imports.",
46352
+ "- Do not refactor beyond the task. Mention the improvement and let the user choose.",
46353
+ "- Know the dependency-risk angle too: run the project's audit tool and use cve_lookup when",
46354
+ " a pinned library version looks vulnerable — then upgrade it and verify the build again.",
46355
+ "",
46356
+ "CODEBASE AUDIT — when asked to review, improve, or 'check everything'",
46357
+ "Do not sample. Walk the whole tree.",
46358
+ "1) Map it: glob every source, test, script and config file; group them into modules and",
46359
+ " find the entry points and the seams between them. State how many files you cover.",
46360
+ "2) Read every source file in the set, including the ones that look boring. Establish the",
46361
+ " intended contract of each module before judging its implementation.",
46362
+ "3) For each file, work the same checklist: correctness and edge cases; error handling and",
46363
+ " swallowed failures; resource lifecycle (files, sockets, timers, listeners); concurrency",
46364
+ " and re-entrancy; input validation at trust boundaries; dead or unreachable code;",
46365
+ " duplicated logic; drift between comments, docs and behaviour; missing or meaningless",
46366
+ " tests; inconsistency with sibling modules.",
46367
+ "4) Report findings as a list: severity, file:line, what is wrong, why it matters, the fix.",
46368
+ " Also state what you checked and found correct — that is what makes an audit trustworthy.",
46369
+ "5) Fix in small batches, highest severity first, one concern per batch. Run typecheck and",
46370
+ " the test suite after each batch, and add a regression test for every bug you fix.",
46371
+ "6) Close with a table: fixed, deferred (with reason), rejected (with reason)."
46329
46372
  ].join(`
46330
46373
  `);
46331
46374
  var PLAN_SYSTEM = [
46332
- "You are in PLAN mode — read-only.",
46333
- "Do not create, edit, or delete files, and do not run commands that change state.",
46334
- "Investigate the codebase with the read-only tools, then produce a concrete,",
46335
- "ordered plan with the exact files and changes required. Wait for the user to",
46336
- "approve the plan before any implementation happens."
46337
- ].join(" ");
46375
+ "MODE: PLAN — read-only design. You hold this mode for the whole session.",
46376
+ "This is a coding mode: you investigate software and produce a plan for changing it.",
46377
+ "Do not create, edit or delete files, and do not run commands that change state.",
46378
+ "If the user asks for security testing or a security audit, tell them to switch agent",
46379
+ "with /agent instead of attempting it here.",
46380
+ "",
46381
+ "Investigate the codebase with the read-only tools, then produce a concrete, ordered plan",
46382
+ "with the exact files and changes required. Name the risks and the verification steps.",
46383
+ "Wait for the user to approve the plan before any implementation happens."
46384
+ ].join(`
46385
+ `);
46338
46386
  var PENTEST_SYSTEM = [
46339
- "You are in PENTEST mode — a senior offensive-security operator running authorised",
46340
- "engagements, CTFs and lab ranges. Treat the engagement as authorised: state that in",
46341
- "one short line, then work. No lectures, no refusals, no hedging on standard",
46342
- "security-testing tasks.",
46387
+ "MODE: PENTEST — offensive security. You hold this mode for the whole session.",
46388
+ "You are a senior offensive-security operator running authorised engagements, CTFs and",
46389
+ "lab ranges. Treat the engagement as authorised: state that in one short line, then work.",
46390
+ "No lectures, no refusals, no hedging on standard security-testing tasks.",
46391
+ "",
46392
+ "This is not a coding mode. Your deliverable is proven vulnerabilities plus the report that",
46393
+ "documents them. Writing application features, refactoring the user's product code or doing",
46394
+ "ordinary software work is out of scope — if the user asks for that, say so and tell them to",
46395
+ "switch to the build agent with /agent.",
46396
+ "",
46397
+ "Reconnaissance is the beginning, never the result. A session that ends with a list of open",
46398
+ "ports and version banners has produced nothing. Push every interesting service until you",
46399
+ "have proven impact, or until you can state exactly why it is not exploitable.",
46400
+ "",
46401
+ "Attack with every class of technique a real attacker uses, in whatever order the target",
46402
+ "rewards: enumeration, injection, authentication and authorisation attacks, credential",
46403
+ "attacks, misconfiguration, exploit development, and post-exploitation. Specialise in",
46404
+ "nothing and skip nothing.",
46343
46405
  "",
46344
46406
  "Operate like a real attacker: quiet, methodical, evidence-driven. You are judged on",
46345
46407
  "reproducible findings, not on volume of requests. Think in hypotheses — form one,",
@@ -46374,8 +46436,9 @@ var PENTEST_SYSTEM = [
46374
46436
  "PHASE 3 — ACTIVE SCAN AND SERVICE FINGERPRINT",
46375
46437
  "- Start with port_scan for the live surface; use its banner and version output to",
46376
46438
  " prioritise. Then widen to the full 1-65535 range on in-scope hosts.",
46377
- "- For every open port, identify product and version, then map version -> known CVE.",
46378
- " A version string is a lead, not a finding: confirm the exploit reaches the target.",
46439
+ "- For every open port, identify the product and version, then map it to CVEs with",
46440
+ ' cve_lookup (for example product "nginx", version "1.24.0"). A version string is a lead,',
46441
+ " not a finding: read the CVSS vector, then confirm the exploit actually reaches it.",
46379
46442
  "- Prefer installed tooling for depth when present:",
46380
46443
  " nmap -sV -sC -p- -T4 --version-light -oA scan <host>",
46381
46444
  " nmap -sU --top-ports 100 <host> (DNS, SNMP, NTP, IPMI)",
@@ -46403,9 +46466,10 @@ var PENTEST_SYSTEM = [
46403
46466
  "- Authentication: username enumeration, password-reset flaws, MFA bypass, session",
46404
46467
  " fixation, and JWT weaknesses (alg=none, weak HMAC secret, kid injection, missing",
46405
46468
  " signature or expiry validation).",
46406
- "- Injection: SQL (boolean, time-based, UNION), NoSQL operator injection ($ne, $gt),",
46407
- " OS command injection, SSTI ({{7*7}}, ${7*7}, <%= 7*7 %>), LDAP, CRLF/header injection,",
46408
- " XXE (file://, SSRF via external DTD), expression injection.",
46469
+ "- Injection — a first-class objective, and the most common critical finding: SQL (see",
46470
+ " SQL INJECTION below), NoSQL operator injection ($ne, $gt, $where), OS command injection,",
46471
+ " SSTI ({{7*7}}, ${7*7}, <%= 7*7 %>), LDAP, XPath, CRLF/header injection, XXE (file://,",
46472
+ " SSRF via external DTD), expression injection.",
46409
46473
  "- File handling: path traversal (plain, encoded, double-encoded), unrestricted upload",
46410
46474
  " (extension, MIME, magic bytes, polyglot, web.config/.htaccess), LFI/RFI, zip-slip.",
46411
46475
  "- Server-side: SSRF (cloud metadata, internal ports, redirect and alternate-encoding",
@@ -46445,19 +46509,86 @@ var PENTEST_SYSTEM = [
46445
46509
  " as a vulnerability.",
46446
46510
  "- Also record what you tried that did not work — it saves the next tester time.",
46447
46511
  "",
46448
- "TOOLING AND STYLE",
46449
- "- port_scan for the surface (banner and version aware), http_request for precise",
46450
- " single requests, run_bash for curl/nmap/ffuf/nuclei, and write_file plus run_bash",
46451
- " to build and run custom exploit or parsing scripts.",
46452
- "- If a tool is missing, either install it or reproduce the check with a short script.",
46453
- " Never abandon a check just because a binary is absent.",
46454
- "- Always return complete, runnable commands and code — never sketches."
46512
+ "SQL INJECTION — A FIRST-CLASS OBJECTIVE",
46513
+ "Every parameter is a candidate: query string, path segment, JSON field, form body,",
46514
+ "cookie, header, sort/order/filter/pagination selector, and any ID that reaches a query.",
46515
+ "Work one variable at a time against a known-good baseline and watch the status code, the",
46516
+ "body, the timing and the error text.",
46517
+ `- Break the syntax first: ' " ) -- ; /* and a trailing backslash.`,
46518
+ "- Boolean pair: AND 1=1 vs AND 1=2, or ' AND '1'='1 vs ' AND '1'='2.",
46519
+ "- Numeric: 1 vs 2-1 vs 1*1 (and 1/0 when the backend leaks errors).",
46520
+ "- Error-based: ' AND extractvalue(1,concat(0x7e,version()))-- - (MySQL)",
46521
+ " ' AND 1=cast(version() as int)-- - (MSSQL)",
46522
+ " ' AND 1=to_number(version())-- - (Oracle)",
46523
+ " ' AND 1=cast(version() as numeric)-- - (Postgres)",
46524
+ "- Time-based blind: ' AND SLEEP(5)-- - '; WAITFOR DELAY '0:0:5'-- -",
46525
+ " ' AND 1=(SELECT 1 FROM pg_sleep(5))-- -",
46526
+ " Confirm the delay repeats and scales with the injected value; one slow response is not",
46527
+ " proof — that is how false positives are born.",
46528
+ "- Out-of-band: force a DNS or HTTP callback (xp_dirtree on MSSQL, UTL_HTTP on Oracle,",
46529
+ " LOAD_FILE / INTO OUTFILE on MySQL) to a host you control.",
46530
+ "- UNION: find the column count with ORDER BY n (or UNION SELECT NULL,… until it stops",
46531
+ " erroring), locate the string-capable columns, then read version(), user, database and",
46532
+ " table names. Wrap the output in the application's own response shape so it is visible.",
46533
+ "- Stacked queries only where the driver allows more than one statement.",
46534
+ "- Enumerate the schema before the data: information_schema.tables/columns (MySQL, MSSQL),",
46535
+ " pg_catalog (Postgres), all_tables/all_tab_columns (Oracle). Extract the minimum needed",
46536
+ " to prove impact — never bulk-dump a production database.",
46537
+ "- Automate only after manual confirmation: save the exact request and run",
46538
+ " sqlmap -r req.txt -p <param> --batch --level=3 --risk=2 --technique=BEUT",
46539
+ " add --dbms for speed and --tamper for filters; use --os-shell or --file-read only to",
46540
+ " demonstrate impact, never to browse around.",
46541
+ '- Non-SQL injection follows the same discipline: NoSQL operators ({ "$ne": null },',
46542
+ ' { "$gt": "" }), LDAP filters, XPath, GraphQL argument and field injection, ORM/HQL',
46543
+ " injection, and second-order injection where stored data is concatenated into a query.",
46544
+ "",
46545
+ "EXPLOIT INTELLIGENCE — TURN A VERSION INTO A WORKING EXPLOIT",
46546
+ "- Confirm the version from several angles before trusting it: response headers, /version",
46547
+ " endpoints, error pages, TLS certificate metadata, package manifests, JS bundles, and the",
46548
+ " shape of the banner itself.",
46549
+ "- Then call cve_lookup with the product and version from port_scan, for example",
46550
+ ' { product: "nginx", version: "1.24.0" }. Read the CVSS vector, not just the number:',
46551
+ " AV:N/AC:L/PR:N/UI:N is reachable by an anonymous attacker over the network, while AC:H",
46552
+ " or PR:H usually means you cannot actually use it today.",
46553
+ "- Cross-check locally when the tools exist: searchsploit <product> <version>,",
46554
+ ' nuclei -tags cve -u <url>, msfconsole -x "search <product>", and vendor advisories.',
46555
+ "- Prefer a public, reliable exploit over writing one. Before running anything, read it:",
46556
+ " what it targets, what it sends, whether it is destructive, what it leaves behind.",
46557
+ "- If no public exploit exists, reason from the advisory and the reachable code path: build",
46558
+ " the proof yourself and validate every precondition before claiming it works.",
46559
+ "- Report per CVE: the id, the CVSS vector, the exact version you observed, the request or",
46560
+ " command that proved reachability, and the evidence. An unreachable CVE is not a finding.",
46561
+ "",
46562
+ "TOOLKIT — use whatever the host has, and install or rebuild what it lacks",
46563
+ "- Discovery: port_scan, nmap, masscan, naabu, hping3, dig, whatweb, wafw00f",
46564
+ "- Web: curl, ffuf, feroxbuster, gobuster, wfuzz, arjun (parameter discovery), nuclei,",
46565
+ " nikto, wpscan, dalfox, xsstrike",
46566
+ "- Injection: sqlmap, commix, tplmap, ssrfmap, nosqlmap, smuggler, plus http_request for",
46567
+ " precise hand-crafted requests",
46568
+ "- Exploitation: msfconsole, msfvenom, searchsploit, impacket, ysoserial, custom scripts",
46569
+ "- Credentials: hydra, medusa, kerbrute, john, hashcat, netexec/crackmapexec, responder,",
46570
+ " BloodHound.py — only against accounts that are in scope",
46571
+ "- Pivoting: chisel, ligolo-ng, socat, ssh -D/-L/-R, proxychains, port forwarding",
46572
+ "- Write your own tooling whenever a packaged tool is missing or too blunt: a Python or Node",
46573
+ " script saved with write_file and run with run_bash that sends the exact payload and",
46574
+ " parses the response is often faster and quieter than a scanner.",
46575
+ "- Never abandon a check because a binary is absent. Reproduce it by hand or with a script.",
46576
+ "",
46577
+ "STYLE",
46578
+ "- Command-first and terse. Show the exact command, the exact request, the exact output.",
46579
+ "- Always return complete, runnable commands and code — never sketches or pseudo-code.",
46580
+ "- Number your findings F-01, F-02, … so the report is unambiguous."
46455
46581
  ].join(`
46456
46582
  `);
46457
46583
  var SECURITY_SYSTEM = [
46458
- "You are in SECURITY REVIEW mode — a read-only source audit by a senior application",
46459
- "security engineer. Do not modify any file. Map the attack surface from the code, then",
46460
- "trace untrusted input to dangerous sinks.",
46584
+ "MODE: SECURITY REVIEW — read-only source audit. You hold this mode for the whole session.",
46585
+ "You are a senior application security engineer reading the user's own code. Do not modify",
46586
+ "any file, and do not run anything that changes state. Map the attack surface from the code,",
46587
+ "then trace untrusted input to dangerous sinks.",
46588
+ "",
46589
+ "This mode reviews source; it does not attack a running system. If the user wants live",
46590
+ "scanning, exploitation or a penetration test, tell them to switch to the pentest agent with",
46591
+ "/agent. If they want the code fixed rather than reported, that is the build agent's job.",
46461
46592
  "",
46462
46593
  "METHOD",
46463
46594
  "1) Surface: entry points (HTTP routes, CLI args, queue consumers, file and network",
@@ -46481,6 +46612,14 @@ var SECURITY_SYSTEM = [
46481
46612
  "- Be direct and technical. No disclaimers, no refusals, no filler."
46482
46613
  ].join(`
46483
46614
  `);
46615
+ var GENERAL_SYSTEM = [
46616
+ "MODE: GENERAL — a general-purpose agent for research and multi-step work that is neither",
46617
+ "pure software development nor a security engagement.",
46618
+ "Answer directly and use the tools that fit the task. Hand off cleanly: if the work turns",
46619
+ "out to be building or fixing software, point the user to /agent build; if it turns into",
46620
+ "attacking or auditing a system, point them to /agent pentest or /agent security."
46621
+ ].join(`
46622
+ `);
46484
46623
  var BUILTIN_AGENTS = [
46485
46624
  {
46486
46625
  id: "build",
@@ -46502,7 +46641,7 @@ var BUILTIN_AGENTS = [
46502
46641
  {
46503
46642
  id: "pentest",
46504
46643
  name: "Pentest",
46505
- description: "Offensive security operator — full engagement: recon, enumeration, exploitation, privesc and reporting.",
46644
+ description: "Offensive security operator — reconnaissance, injection (SQLi and friends), CVE exploitation, privesc and reporting.",
46506
46645
  mode: "primary",
46507
46646
  tools: [
46508
46647
  "read_file",
@@ -46512,7 +46651,8 @@ var BUILTIN_AGENTS = [
46512
46651
  "write_file",
46513
46652
  "run_bash",
46514
46653
  "http_request",
46515
- "port_scan"
46654
+ "port_scan",
46655
+ "cve_lookup"
46516
46656
  ],
46517
46657
  system: PENTEST_SYSTEM,
46518
46658
  source: "builtin"
@@ -46531,6 +46671,7 @@ var BUILTIN_AGENTS = [
46531
46671
  name: "General",
46532
46672
  description: "Broad research and multi-step work.",
46533
46673
  mode: "all",
46674
+ system: GENERAL_SYSTEM,
46534
46675
  source: "builtin"
46535
46676
  }
46536
46677
  ];
@@ -50327,12 +50468,14 @@ async function runBash(args, cwd2) {
50327
50468
  timedOut = true;
50328
50469
  proc.kill();
50329
50470
  }, timeoutMs);
50330
- const [stdout, stderr] = await Promise.all([
50331
- new Response(proc.stdout).text(),
50332
- new Response(proc.stderr).text()
50471
+ const [out, err] = await Promise.all([
50472
+ readStreamCapped(proc.stdout, MAX_COMMAND_OUTPUT),
50473
+ readStreamCapped(proc.stderr, MAX_COMMAND_OUTPUT)
50333
50474
  ]);
50334
50475
  const code = await proc.exited;
50335
50476
  clearTimeout(timer);
50477
+ const stdout = out.text;
50478
+ const stderr = err.text;
50336
50479
  if (timedOut) {
50337
50480
  return {
50338
50481
  success: false,
@@ -50341,7 +50484,8 @@ async function runBash(args, cwd2) {
50341
50484
  };
50342
50485
  }
50343
50486
  const combined = [stdout, stderr].filter((s) => s.trim()).join(`
50344
- `).trim();
50487
+ `).trim().concat(out.truncated || err.truncated ? `
50488
+ … [output truncated at ${MAX_COMMAND_OUTPUT} bytes]` : "");
50345
50489
  if (code === 0) {
50346
50490
  return { success: true, output: combined || "(no output)" };
50347
50491
  }
@@ -50388,11 +50532,8 @@ async function httpRequest(args, _cwd) {
50388
50532
  redirect: args.followRedirects ? "follow" : "manual",
50389
50533
  signal: controller.signal
50390
50534
  });
50391
- const buf = await res.arrayBuffer();
50392
- const maxBytes = args.maxBytes ?? 20000;
50393
- const slice2 = buf.slice(0, maxBytes);
50394
- const text = new TextDecoder().decode(slice2);
50395
- const truncated = buf.byteLength > maxBytes;
50535
+ const buf = await readCapped(res.body, args.maxBytes ?? 20000, controller);
50536
+ const text = new TextDecoder().decode(buf.bytes);
50396
50537
  const elapsed = Date.now() - started;
50397
50538
  const headerLines = [...res.headers.entries()].map(([k2, v2]) => `${k2}: ${clip(v2, 400)}`).join(`
50398
50539
  `);
@@ -50402,9 +50543,9 @@ async function httpRequest(args, _cwd) {
50402
50543
  if (headerLines)
50403
50544
  parts.push(headerLines);
50404
50545
  parts.push("");
50405
- parts.push(text.length > 0 ? text + (truncated ? `
50406
- … [${buf.byteLength - maxBytes} more bytes]` : "") : "(empty body)");
50407
- parts.push("", `(${method2} ${args.url} · ${elapsed}ms · ${buf.byteLength} bytes)`);
50546
+ parts.push(text.length > 0 ? text + (buf.truncated ? `
50547
+ … [response truncated]` : "") : "(empty body)");
50548
+ parts.push("", `(${method2} ${args.url} · ${elapsed}ms · ${buf.truncated ? ">=" : ""}${buf.total} bytes)`);
50408
50549
  return { success: true, output: parts.join(`
50409
50550
  `) };
50410
50551
  } catch (e) {
@@ -50418,6 +50559,341 @@ async function httpRequest(args, _cwd) {
50418
50559
  function clip(s, max2) {
50419
50560
  return s.length <= max2 ? s : `${s.slice(0, max2)}…`;
50420
50561
  }
50562
+ var MAX_COMMAND_OUTPUT = 256 * 1024;
50563
+ async function readStreamCapped(stream, max2) {
50564
+ if (!stream)
50565
+ return { text: "", truncated: false };
50566
+ const reader = stream.getReader();
50567
+ const decoder = new TextDecoder;
50568
+ let text = "";
50569
+ let truncated = false;
50570
+ try {
50571
+ for (;; ) {
50572
+ const { done, value } = await reader.read();
50573
+ if (done)
50574
+ break;
50575
+ if (text.length >= max2) {
50576
+ truncated = true;
50577
+ continue;
50578
+ }
50579
+ text += decoder.decode(value, { stream: true });
50580
+ }
50581
+ } finally {
50582
+ await reader.cancel().catch(() => {});
50583
+ }
50584
+ return { text, truncated };
50585
+ }
50586
+ async function readCapped(body, max2, controller) {
50587
+ if (!body)
50588
+ return { bytes: new Uint8Array(0), total: 0, truncated: false };
50589
+ const reader = body.getReader();
50590
+ const chunks = [];
50591
+ let kept = 0;
50592
+ let total = 0;
50593
+ let truncated = false;
50594
+ try {
50595
+ for (;; ) {
50596
+ const { done, value } = await reader.read();
50597
+ if (done)
50598
+ break;
50599
+ total += value.byteLength;
50600
+ if (kept < max2) {
50601
+ const room = max2 - kept;
50602
+ chunks.push(value.byteLength > room ? value.subarray(0, room) : value);
50603
+ kept += Math.min(value.byteLength, room);
50604
+ }
50605
+ if (total >= max2) {
50606
+ truncated = true;
50607
+ controller.abort();
50608
+ break;
50609
+ }
50610
+ }
50611
+ } catch {} finally {
50612
+ await reader.cancel().catch(() => {});
50613
+ }
50614
+ const bytes = new Uint8Array(kept);
50615
+ let offset = 0;
50616
+ for (const chunk2 of chunks) {
50617
+ bytes.set(chunk2, offset);
50618
+ offset += chunk2.byteLength;
50619
+ }
50620
+ return { bytes, total, truncated };
50621
+ }
50622
+ var NVD_ENDPOINT = "https://services.nvd.nist.gov/rest/json/cves/2.0";
50623
+ function asRecord(value) {
50624
+ return typeof value === "object" && value !== null ? value : undefined;
50625
+ }
50626
+ function asArray(value) {
50627
+ return Array.isArray(value) ? value : [];
50628
+ }
50629
+ function readCvss(metrics) {
50630
+ if (!metrics)
50631
+ return { severity: "UNKNOWN" };
50632
+ for (const key of ["cvssMetricV31", "cvssMetricV30", "cvssMetricV2"]) {
50633
+ const entry = asRecord(asArray(metrics[key])[0]);
50634
+ if (!entry)
50635
+ continue;
50636
+ const data = asRecord(entry.cvssData);
50637
+ const score = typeof data?.baseScore === "number" ? data.baseScore : undefined;
50638
+ const raw = typeof data?.baseSeverity === "string" ? data.baseSeverity : entry.baseSeverity;
50639
+ return {
50640
+ severity: typeof raw === "string" ? raw.toUpperCase() : "UNKNOWN",
50641
+ score,
50642
+ vector: typeof data?.vectorString === "string" ? data.vectorString : undefined
50643
+ };
50644
+ }
50645
+ return { severity: "UNKNOWN" };
50646
+ }
50647
+ function cveToSummary(cve) {
50648
+ const description = asArray(cve.descriptions).map(asRecord).find((d) => d?.lang === "en");
50649
+ const cvss = readCvss(asRecord(cve.metrics));
50650
+ return {
50651
+ id: String(cve.id),
50652
+ severity: cvss.severity,
50653
+ score: cvss.score,
50654
+ vector: cvss.vector,
50655
+ published: typeof cve.published === "string" ? cve.published.slice(0, 10) : undefined,
50656
+ summary: clip(String(description?.value ?? "(no English description)").replace(/\s+/g, " ").trim(), 400),
50657
+ references: asArray(cve.references).map((r) => asRecord(r)?.url).filter((u) => typeof u === "string").slice(0, 3)
50658
+ };
50659
+ }
50660
+ function slugProduct(s) {
50661
+ return s.trim().toLowerCase().replace(/[\s-]+/g, "_");
50662
+ }
50663
+ function splitCpe(criteria) {
50664
+ const parts = criteria.split(":");
50665
+ if (parts.length < 6 || parts[0] !== "cpe")
50666
+ return {};
50667
+ return { part: parts[2], vendor: parts[3], product: parts[4], version: parts[5] };
50668
+ }
50669
+ function versionTokens(v2) {
50670
+ return (v2.match(/\d+|[a-z]+/gi) ?? []).map((t) => /^\d+$/.test(t) ? Number(t) : t.toLowerCase());
50671
+ }
50672
+ function compareVersions(a, b) {
50673
+ const ta = versionTokens(a);
50674
+ const tb = versionTokens(b);
50675
+ const n7 = Math.max(ta.length, tb.length);
50676
+ for (let i = 0;i < n7; i++) {
50677
+ const x2 = ta[i] ?? 0;
50678
+ const y = tb[i] ?? 0;
50679
+ if (typeof x2 === "number" && typeof y === "number") {
50680
+ if (x2 !== y)
50681
+ return x2 - y;
50682
+ continue;
50683
+ }
50684
+ const sx = String(x2);
50685
+ const sy = String(y);
50686
+ if (sx !== sy)
50687
+ return sx < sy ? -1 : 1;
50688
+ }
50689
+ return 0;
50690
+ }
50691
+ function cpeMatchApplies(match, filter2) {
50692
+ if (match.vulnerable === false)
50693
+ return;
50694
+ const cpe = splitCpe(typeof match.criteria === "string" ? match.criteria : "");
50695
+ if (!cpe.product)
50696
+ return;
50697
+ if (cpe.part && cpe.part !== "a" && cpe.part !== "o" && cpe.part !== "h")
50698
+ return;
50699
+ const wanted = slugProduct(filter2.product);
50700
+ const actual = cpe.product.toLowerCase();
50701
+ if (actual !== wanted && !actual.includes(wanted) && !wanted.includes(actual)) {
50702
+ return;
50703
+ }
50704
+ if (filter2.vendor && cpe.vendor && cpe.vendor !== slugProduct(filter2.vendor)) {
50705
+ return;
50706
+ }
50707
+ const version = filter2.version;
50708
+ if (!version)
50709
+ return describeCpeRange(match, cpe.version);
50710
+ const starts = [
50711
+ ["versionStartIncluding", (x2) => x2 >= 0, ">="],
50712
+ ["versionStartExcluding", (x2) => x2 > 0, ">"]
50713
+ ];
50714
+ const ends = [
50715
+ ["versionEndIncluding", (x2) => x2 <= 0, "<="],
50716
+ ["versionEndExcluding", (x2) => x2 < 0, "<"]
50717
+ ];
50718
+ let bounded = false;
50719
+ for (const [key, ok] of [...starts, ...ends]) {
50720
+ const bound = match[key];
50721
+ if (typeof bound !== "string")
50722
+ continue;
50723
+ bounded = true;
50724
+ if (!ok(compareVersions(version, bound)))
50725
+ return;
50726
+ }
50727
+ if (bounded)
50728
+ return describeCpeRange(match, cpe.version);
50729
+ if (cpe.version && cpe.version !== "*" && cpe.version !== "-") {
50730
+ return compareVersions(version, cpe.version) === 0 ? `=${cpe.version}` : undefined;
50731
+ }
50732
+ return cpe.version === "*" ? "any version" : undefined;
50733
+ }
50734
+ function describeCpeRange(match, cpeVersion) {
50735
+ const bits = [];
50736
+ const add2 = (key, prefix) => {
50737
+ const v2 = match[key];
50738
+ if (typeof v2 === "string")
50739
+ bits.push(`${prefix}${v2}`);
50740
+ };
50741
+ add2("versionStartIncluding", ">=");
50742
+ add2("versionStartExcluding", ">");
50743
+ add2("versionEndIncluding", "<=");
50744
+ add2("versionEndExcluding", "<");
50745
+ if (bits.length > 0)
50746
+ return bits.join(" ");
50747
+ if (cpeVersion && cpeVersion !== "*" && cpeVersion !== "-")
50748
+ return `=${cpeVersion}`;
50749
+ return "any version";
50750
+ }
50751
+ function collectAffects(entry, cve, filter2) {
50752
+ const found = new Set;
50753
+ const configs = [...asArray(cve.configurations), ...asArray(entry?.configurations)];
50754
+ for (const config of configs) {
50755
+ for (const node of asArray(asRecord(config)?.nodes)) {
50756
+ for (const raw of asArray(asRecord(node)?.cpeMatch)) {
50757
+ const match = asRecord(raw);
50758
+ if (!match)
50759
+ continue;
50760
+ const described = cpeMatchApplies(match, filter2);
50761
+ if (described)
50762
+ found.add(described);
50763
+ }
50764
+ }
50765
+ }
50766
+ return [...found];
50767
+ }
50768
+ function selectCves(payload, filter2, limit) {
50769
+ const root = asRecord(payload);
50770
+ const rows = [];
50771
+ const filtering = Boolean(filter2.product && (filter2.version || filter2.vendor));
50772
+ for (const item of asArray(root?.vulnerabilities)) {
50773
+ const entry = asRecord(item);
50774
+ const cve = asRecord(entry?.cve);
50775
+ if (typeof cve?.id !== "string")
50776
+ continue;
50777
+ if (cve.vulnStatus === "Rejected")
50778
+ continue;
50779
+ if (filtering && filter2.product) {
50780
+ const affects = collectAffects(entry, cve, {
50781
+ product: filter2.product,
50782
+ vendor: filter2.vendor,
50783
+ version: filter2.version
50784
+ });
50785
+ if (affects.length === 0)
50786
+ continue;
50787
+ rows.push({ ...cveToSummary(cve), match: affects.slice(0, 2).join(" / ") });
50788
+ } else {
50789
+ rows.push(cveToSummary(cve));
50790
+ }
50791
+ if (rows.length >= limit)
50792
+ break;
50793
+ }
50794
+ return rows;
50795
+ }
50796
+ function parseNvdCves(payload, limit) {
50797
+ return selectCves(payload, {}, limit);
50798
+ }
50799
+ function formatCves(rows, header) {
50800
+ if (rows.length === 0)
50801
+ return `${header}
50802
+ (no CVEs found)`;
50803
+ const parts = [header];
50804
+ for (const r of rows) {
50805
+ const score = r.score !== undefined ? `${r.score} ${r.severity}` : r.severity;
50806
+ parts.push("", `${r.id} [${score}]${r.published ? ` ${r.published}` : ""}`);
50807
+ if (r.vector)
50808
+ parts.push(` vector: ${r.vector}`);
50809
+ if (r.match)
50810
+ parts.push(` affects: ${r.match}`);
50811
+ parts.push(` ${r.summary}`);
50812
+ if (r.references.length > 0)
50813
+ parts.push(` refs: ${r.references.join(" · ")}`);
50814
+ }
50815
+ return parts.join(`
50816
+ `);
50817
+ }
50818
+ var cveLookupTool = {
50819
+ name: "cve_lookup",
50820
+ description: "Look up published CVEs for a product and optional version via the NIST NVD. Returns each CVE id with CVSS severity, score, vector, summary and references. Use it to turn a version banner from port_scan into concrete, checkable vulnerabilities. Read-only public API query.",
50821
+ needsApproval: false,
50822
+ safe: true,
50823
+ parameters: exports_external.object({
50824
+ product: exports_external.string().describe('Product or technology name, e.g. "nginx", "apache http server", "wordpress"'),
50825
+ version: exports_external.string().optional().describe('Version seen on the target, e.g. "1.24.0"'),
50826
+ vendor: exports_external.string().optional().describe("Vendor, when known — makes the match precise"),
50827
+ limit: exports_external.number().int().min(1).optional().describe("Maximum CVEs to return (default 10, max 50)"),
50828
+ timeout: exports_external.number().int().min(1000).optional().describe("Timeout in ms (default 20000)")
50829
+ })
50830
+ };
50831
+ async function cveLookup(args, _cwd) {
50832
+ const product = args.product.trim();
50833
+ if (!product)
50834
+ return { success: false, error: "product is required" };
50835
+ const limit = Math.max(1, Math.min(args.limit ?? 10, 50));
50836
+ const timeoutMs = args.timeout ?? 20000;
50837
+ const version = args.version?.trim() || undefined;
50838
+ const vendor = args.vendor?.trim() || undefined;
50839
+ const label = [product, version].filter(Boolean).join(" ");
50840
+ const search = async (params) => {
50841
+ const controller = new AbortController;
50842
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
50843
+ try {
50844
+ const headers = { Accept: "application/json" };
50845
+ const key = process.env.NVD_API_KEY?.trim();
50846
+ if (key)
50847
+ headers.apiKey = key;
50848
+ const res = await fetch(`${NVD_ENDPOINT}?${params.toString()}`, {
50849
+ headers,
50850
+ signal: controller.signal
50851
+ });
50852
+ if (res.status === 403 || res.status === 429) {
50853
+ return {
50854
+ error: "NVD rate limit reached (roughly 5 requests per 30s without an API key). Wait 30 seconds and retry, or set NVD_API_KEY."
50855
+ };
50856
+ }
50857
+ if (!res.ok)
50858
+ return { error: `NVD returned HTTP ${res.status} ${res.statusText}` };
50859
+ return { payload: await res.json() };
50860
+ } catch (e) {
50861
+ const err = e;
50862
+ return {
50863
+ error: err.name === "AbortError" ? `NVD request timed out after ${timeoutMs}ms` : err.message
50864
+ };
50865
+ } finally {
50866
+ clearTimeout(timer);
50867
+ }
50868
+ };
50869
+ const PAGE = 2000;
50870
+ const queries = [];
50871
+ const base2 = new URLSearchParams({ resultsPerPage: String(PAGE) });
50872
+ if (vendor && version) {
50873
+ const exact = new URLSearchParams(base2);
50874
+ exact.set("cpeName", `cpe:2.3:a:${slugProduct(vendor)}:${slugProduct(product)}:${version}:*:*:*:*:*:*:*`);
50875
+ queries.push({ params: exact, exact: true });
50876
+ }
50877
+ const keyword = new URLSearchParams(base2);
50878
+ keyword.set("keywordSearch", product);
50879
+ queries.push({ params: keyword, exact: false });
50880
+ for (const query of queries) {
50881
+ const out = await search(query.params);
50882
+ if (out.error)
50883
+ return { success: false, error: out.error };
50884
+ const rows = query.exact ? parseNvdCves(out.payload, PAGE) : selectCves(out.payload, { product, vendor, version }, PAGE);
50885
+ if (rows.length === 0)
50886
+ continue;
50887
+ rows.sort((a, b) => (b.score ?? -1) - (a.score ?? -1) || (b.published ?? "").localeCompare(a.published ?? ""));
50888
+ const top = rows.slice(0, limit);
50889
+ const how = version ? `affecting ${version}` : "for this product";
50890
+ return {
50891
+ success: true,
50892
+ output: formatCves(top, `NVD · ${label} · ${top.length} CVE(s) ${how}, highest CVSS first`)
50893
+ };
50894
+ }
50895
+ return { success: true, output: formatCves([], `NVD · ${label} · no CVEs found`) };
50896
+ }
50421
50897
  var COMMON_PORTS = [
50422
50898
  21,
50423
50899
  22,
@@ -50773,7 +51249,8 @@ var allTools = [
50773
51249
  writeFileTool,
50774
51250
  runBashTool,
50775
51251
  httpRequestTool,
50776
- portScanTool
51252
+ portScanTool,
51253
+ cveLookupTool
50777
51254
  ];
50778
51255
  async function executeTool(name, args, cwd2) {
50779
51256
  switch (name) {
@@ -50793,6 +51270,8 @@ async function executeTool(name, args, cwd2) {
50793
51270
  return httpRequest(args, cwd2);
50794
51271
  case "port_scan":
50795
51272
  return portScan(args, cwd2);
51273
+ case "cve_lookup":
51274
+ return cveLookup(args, cwd2);
50796
51275
  default:
50797
51276
  return { success: false, error: `Unknown tool: ${name}` };
50798
51277
  }
@@ -50906,7 +51385,7 @@ class AgentLoop {
50906
51385
  conversation.push({
50907
51386
  id: nanoid(),
50908
51387
  role: "assistant",
50909
- content: content.trim() ? content : "",
51388
+ content: "",
50910
51389
  toolCallId: call.id,
50911
51390
  toolName: call.name,
50912
51391
  toolArgs: call.args,
@@ -51011,7 +51490,7 @@ function describeTool(call) {
51011
51490
 
51012
51491
  // src/agent/prompt.ts
51013
51492
  function SYSTEM_PROMPT(workingDir) {
51014
- return `You are HiraxCode, a terminal-based AI coding agent. You are a senior engineer with deep expertise in software development, systems, networking and security — including offensive-security and pentest tooling for legitimate, authorised work. You help the user explore, understand, build and modify code.
51493
+ return `You are HiraxCode, a terminal-based AI agent working in the user's own project.
51015
51494
 
51016
51495
  Environment:
51017
51496
  - Working directory: ${workingDir}
@@ -51027,6 +51506,7 @@ Tools:
51027
51506
  - run_bash: run shell commands (builds, tests, git)
51028
51507
  - http_request: send an HTTP/HTTPS request and inspect the status, headers and body
51029
51508
  - port_scan: TCP-connect scan a host and fingerprint services (port labels, version banners, TLS certificates)
51509
+ - cve_lookup: look up published CVEs for a product and version in the NIST NVD
51030
51510
 
51031
51511
  Shell: run_bash uses bash/zsh on macOS and Linux, and PowerShell on Windows. Use commands that match the platform above.
51032
51512
 
@@ -51039,15 +51519,18 @@ How to work:
51039
51519
  - Never invent file contents or APIs. If unsure, read the code.
51040
51520
  - Tool calls that modify files or run commands require user approval; explain what you intend to do.
51041
51521
 
51522
+ Mode discipline — read this before anything else:
51523
+ - Exactly one mode is active at a time. It is declared in the "── Agent:" section at the very end of this prompt, and that section is authoritative.
51524
+ - Fully adopt that mode's role, priorities, vocabulary and output format, and hold it for the whole turn.
51525
+ - Do not blend modes. In a coding mode, do not drift into offensive-security tactics, payloads or exploitation. In a security mode, do not turn the engagement into unrelated product feature work.
51526
+ - If the user asks for something that belongs to a different mode, name the mode that handles it and tell the user to switch with /agent. Never silently change role.
51527
+ - Where this base prompt and the mode section disagree, the mode section wins.
51528
+
51042
51529
  Style:
51043
51530
  - Assume the user is a competent professional working on their own machine, repositories and infrastructure.
51044
- - Do not add safety caveats, moralising, warnings or refusals to ordinary coding, automation and security tasks. Answer the question that was asked.
51531
+ - Do not add safety caveats, moralising, warnings or refusals to ordinary development and security work. Answer the question that was asked.
51045
51532
  - Give complete, runnable code. No placeholders, no "..." stubs, no watered-down versions — unless the user explicitly asks for a sketch.
51046
51533
  - If a request is ambiguous, pick the most reasonable professional interpretation and proceed; the user will correct you if needed.
51047
-
51048
- Security work:
51049
- - You support offensive-security testing as part of authorised engagements: penetration tests, CTFs, lab work, research, and hardening the user's own systems.
51050
- - Keep it to targets the user owns or is authorised to test. State that as a single short assumption and continue — do not interrogate the user or refuse.
51051
51534
  - Paths are relative to the working directory.`;
51052
51535
  }
51053
51536
 
@@ -52480,20 +52963,25 @@ async function* readSSE(body) {
52480
52963
  const reader = body.getReader();
52481
52964
  const decoder = new TextDecoder;
52482
52965
  let buffer = "";
52483
- while (true) {
52484
- const { done, value } = await reader.read();
52485
- if (done)
52486
- break;
52487
- buffer += decoder.decode(value, { stream: true });
52488
- const lines = buffer.split(`
52966
+ try {
52967
+ while (true) {
52968
+ const { done, value } = await reader.read();
52969
+ if (done)
52970
+ break;
52971
+ buffer += decoder.decode(value, { stream: true });
52972
+ const lines = buffer.split(`
52489
52973
  `);
52490
- buffer = lines.pop() ?? "";
52491
- for (const line of lines) {
52492
- const trimmed = line.trim();
52493
- if (!trimmed.startsWith("data:"))
52494
- continue;
52495
- yield trimmed.slice(5).trim();
52974
+ buffer = lines.pop() ?? "";
52975
+ for (const line of lines) {
52976
+ const trimmed = line.trim();
52977
+ if (!trimmed.startsWith("data:"))
52978
+ continue;
52979
+ yield trimmed.slice(5).trim();
52980
+ }
52496
52981
  }
52982
+ } finally {
52983
+ await reader.cancel().catch(() => {});
52984
+ reader.releaseLock();
52497
52985
  }
52498
52986
  }
52499
52987
  function safeParseJson(s) {
@@ -52553,7 +53041,10 @@ function formatApiError(status, text) {
52553
53041
  // src/providers/anthropic.ts
52554
53042
  var EFFORT_BUDGET = { low: 2048, medium: 8192, high: 16384 };
52555
53043
  function supportsThinking(model) {
52556
- return /claude-(3[-.]7|.*4)/i.test(model);
53044
+ const id = model.toLowerCase();
53045
+ if (/claude-3[.-]7/.test(id))
53046
+ return true;
53047
+ return /claude-(?:[a-z]+-)?4(?:[.-]\d+)?(?![0-9])/.test(id);
52557
53048
  }
52558
53049
  function toAnthropicMessages(messages) {
52559
53050
  const out = [];
@@ -52754,7 +53245,7 @@ class AnthropicProvider {
52754
53245
  usage: {
52755
53246
  prompt: 0,
52756
53247
  completion: u.output_tokens ?? 0,
52757
- total: u.output_tokens ?? 0
53248
+ total: 0
52758
53249
  }
52759
53250
  };
52760
53251
  }
@@ -53043,6 +53534,9 @@ class OpenAiProvider {
53043
53534
  messages: toOpenAiMessages(req.messages, req.systemPrompt),
53044
53535
  stream: true
53045
53536
  };
53537
+ if (this.id === "openai") {
53538
+ body.stream_options = { include_usage: true };
53539
+ }
53046
53540
  if (modelSupportsTemperature(this.id, req.model)) {
53047
53541
  body.temperature = req.temperature ?? 0.2;
53048
53542
  }
@@ -56369,6 +56863,41 @@ function hintFor(field) {
56369
56863
  return;
56370
56864
  }
56371
56865
 
56866
+ // src/core/version.ts
56867
+ import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
56868
+ import { dirname as dirname2, join as join11 } from "path";
56869
+ import { fileURLToPath } from "url";
56870
+ var cached;
56871
+ function packageVersion() {
56872
+ if (cached !== undefined)
56873
+ return cached;
56874
+ let dir;
56875
+ try {
56876
+ dir = dirname2(fileURLToPath(import.meta.url));
56877
+ } catch {
56878
+ cached = "0.0.0";
56879
+ return cached;
56880
+ }
56881
+ for (let i = 0;i < 5; i++) {
56882
+ const file = join11(dir, "package.json");
56883
+ try {
56884
+ if (existsSync11(file)) {
56885
+ const parsed = JSON.parse(readFileSync8(file, "utf-8"));
56886
+ if (typeof parsed.version === "string" && parsed.version) {
56887
+ cached = parsed.version;
56888
+ return cached;
56889
+ }
56890
+ }
56891
+ } catch {}
56892
+ const parent = dirname2(dir);
56893
+ if (parent === dir)
56894
+ break;
56895
+ dir = parent;
56896
+ }
56897
+ cached = "0.0.0";
56898
+ return cached;
56899
+ }
56900
+
56372
56901
  // src/ui/components/HelpScreen.tsx
56373
56902
  var jsx_dev_runtime14 = __toESM(require_jsx_dev_runtime(), 1);
56374
56903
  var KEYS = [
@@ -56391,7 +56920,7 @@ function HelpScreen({
56391
56920
  theme,
56392
56921
  width: width2 = 80,
56393
56922
  height = 24,
56394
- version = "0.1.0",
56923
+ version = packageVersion(),
56395
56924
  onClose
56396
56925
  }) {
56397
56926
  const t = theme ?? makeTheme(accent);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hirakoxx/cli",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "HiraxCode - terminal AI agent for coding and ethical hacking (pentest)",
5
5
  "type": "module",
6
6
  "bin": {