nexrall-code 0.5.72 → 0.5.74

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 (2) hide show
  1. package/dist/index.js +111 -19
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -9873,6 +9873,7 @@ var require_client = __commonJS({
9873
9873
  exports.streamChat = streamChat;
9874
9874
  exports.cancelTurn = cancelTurn;
9875
9875
  exports.revokeRefreshToken = revokeRefreshToken2;
9876
+ exports.describeAttachment = describeAttachment2;
9876
9877
  exports.getBalance = getBalance3;
9877
9878
  exports.exchangeVscodeCode = exchangeVscodeCode;
9878
9879
  exports.login = login2;
@@ -10596,6 +10597,37 @@ var require_client = __commonJS({
10596
10597
  } catch {
10597
10598
  }
10598
10599
  }
10600
+ async function describeAttachment2(kind, data, mediaType, name) {
10601
+ const fetchOnce = () => (0, node_fetch_1.default)(`${exports.API_BASE}/api/code/assets/describe`, {
10602
+ method: "POST",
10603
+ headers: { ...authHeaders(), "Content-Type": "application/json" },
10604
+ body: JSON.stringify({ kind, data, media_type: mediaType, name })
10605
+ });
10606
+ let response = await fetchOnce();
10607
+ if (response.status === 401 || response.status === 403) {
10608
+ const body = await response.text().catch(() => "");
10609
+ if (isExpiredTokenResponse(response.status, body) && await refreshAccessToken()) {
10610
+ response = await fetchOnce();
10611
+ } else {
10612
+ throw new Error(`API error ${response.status}: ${body}`);
10613
+ }
10614
+ }
10615
+ if (!response.ok) {
10616
+ let message = `API error ${response.status}`;
10617
+ try {
10618
+ const body = await response.json();
10619
+ if (body?.error)
10620
+ message = body.error;
10621
+ } catch {
10622
+ }
10623
+ throw new Error(message);
10624
+ }
10625
+ const json = await response.json();
10626
+ return {
10627
+ text: typeof json.text === "string" ? json.text : "",
10628
+ balance: typeof json.balance === "number" ? json.balance : 0
10629
+ };
10630
+ }
10599
10631
  async function getBalance3() {
10600
10632
  const fetchOnce = () => (0, node_fetch_1.default)(`${exports.API_BASE}/api/code/balance`, { method: "GET", headers: authHeaders() });
10601
10633
  let response = await fetchOnce();
@@ -10895,7 +10927,25 @@ var require_securityLint = __commonJS({
10895
10927
  // SCREAMING_SNAKE identifier (an env-var name) is excluded, along with the
10896
10928
  // usual placeholder vocabulary. The value must also look like actual secret
10897
10929
  // material: mixed case or digits, not a lone lowercase word.
10898
- re: /(?:password|passwd|secret|api[_-]?key|apikey|access[_-]?token)['"`]?\s*[:=]\s*['"`](?![A-Z0-9_]+['"`])(?!.*(?:\$\{|process\.env|os\.environ|example|changeme|placeholder|redacted|xxx|test|dummy|fake|sample|your[_-]?|<|\*{3}))(?=[^'"`]*[0-9A-Z])[^'"`\s]{10,}['"`]/,
10930
+ //
10931
+ // `\{\{` was added after porting this module to the Nexrall backend and
10932
+ // re-measuring there: the only hit across 100 route/service files was
10933
+ // `"x-api-key": "{{PAYOS_API_KEY}}"` inside a connector's DOCUMENTATION
10934
+ // string — a mustache/handlebars placeholder, i.e. the opposite of a leaked
10935
+ // credential. `${...}` was already excluded for exactly this reason; this is
10936
+ // the same idea for the other common templating syntax, and it cannot mask a
10937
+ // real secret because a literal `{{` never appears inside one.
10938
+ //
10939
+ // EXCLUSIONS ARE SCOPED TO THE VALUE (`[^'"`]*`), NOT THE WHOLE LINE (`.*`).
10940
+ // Found by the tests written for the Nexrall backend port: the lookahead
10941
+ // scanned the entire rest of the line, so ANY trailing comment containing a
10942
+ // placeholder word silenced a genuine finding — `const pw = "<real secret>";
10943
+ // // xxx` went SILENT, as did `// test`. An agent writing production code
10944
+ // adds trailing comments constantly, so this was not a rare edge case.
10945
+ // Bounding the lookahead at the closing quote keeps the placeholder
10946
+ // vocabulary matching the VALUE (the only place it was ever meant to) while
10947
+ // making a trailing comment irrelevant.
10948
+ re: /(?:password|passwd|secret|api[_-]?key|apikey|access[_-]?token)['"`]?\s*[:=]\s*['"`](?![A-Z0-9_]+['"`])(?![^'"`]*(?:\$\{|\{\{|process\.env|os\.environ|example|changeme|placeholder|redacted|xxx|test|dummy|fake|sample|your[_-]?|<|\*{3}))(?=[^'"`]*[0-9A-Z])[^'"`\s]{10,}['"`]/,
10899
10949
  message: "Hardcoded credential literal. Read it from the environment/secret store instead, and rotate the exposed value.",
10900
10950
  includeComments: true
10901
10951
  },
@@ -10925,7 +10975,23 @@ var require_securityLint = __commonJS({
10925
10975
  // outside: req/request/params/query/body/input/user/args, or a bare
10926
10976
  // `'...' + ident`. This trades recall for precision on purpose — thorough SQL
10927
10977
  // review is the security-auditor agent's job, not an inline regex's.
10928
- re: /\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM)\b[^;'"`]{0,160}(?:\$\{\s*(?:req|request|params?|query|body|input|user|args|ctx)\b|['"`]\s*\+\s*(?:req|request|params?|query|body|input|user|args|ctx)\b|%\s*\(\s*(?:request|params?|query|body|input|user)\b)/i,
10978
+ //
10979
+ // THE GAP IS PER-ALTERNATIVE, and that asymmetry is load-bearing. The concat
10980
+ // branch originally shared the no-quote gap, which made it miss the QUOTED
10981
+ // concatenation form — the more dangerous one, since the value lands inside
10982
+ // a string literal where a lone `'` breaks out of the query:
10983
+ //
10984
+ // "DELETE FROM s WHERE t = " + query.token -> flagged
10985
+ // "DELETE FROM s WHERE t = '" + query.token + "'" -> MISSED
10986
+ //
10987
+ // Loosening the gap for ALL branches fixes that but costs precision, measured
10988
+ // on the Nexrall backend: the `${…}` branch then travels PAST the end of the
10989
+ // SQL string to a later interpolation, flagging correctly-parameterised
10990
+ // queries like `UPDATE users SET ${sets.join(',')} WHERE id=$${params.length}`
10991
+ // (0/100 flagged files became 2/100). So only the concat branch gets the loose
10992
+ // gap — it must cross a quote by construction — while the others stay confined
10993
+ // to a single string segment.
10994
+ re: /\b(?:SELECT|INSERT\s+INTO|UPDATE|DELETE\s+FROM)\b(?:[^;'"`]{0,160}(?:\$\{\s*(?:req|request|params?|query|body|input|user|args|ctx)\b|%\s*\(\s*(?:request|params?|query|body|input|user)\b)|[^;]{0,160}['"`]\s*\+\s*(?:req|request|params?|query|body|input|user|args|ctx)\b)/i,
10929
10995
  message: "SQL built by interpolating a request-derived value. Use a parameterised query ($1 / ? placeholders) \u2014 this is the classic injection sink."
10930
10996
  },
10931
10997
  {
@@ -14510,6 +14576,30 @@ ${lines.join("\n")}`;
14510
14576
  return { output: `Found ${photos.length} photo(s) for "${query}":
14511
14577
  ${lines.join("\n")}` };
14512
14578
  }
14579
+ async function webSearch(input) {
14580
+ const query = typeof input.query === "string" ? input.query.trim() : "";
14581
+ if (!query)
14582
+ return { error: "Missing required parameter: query" };
14583
+ const token = (0, auth_1.getToken)();
14584
+ if (!token)
14585
+ return { error: "Not authenticated. Run `nexrall-code login` first." };
14586
+ try {
14587
+ const r2 = await _httpsPost(`${client_1.API_BASE}/api/code/tools/web_search`, JSON.stringify({ query }), { "Content-Type": "application/json", Authorization: `Bearer ${token}` }, 3e4);
14588
+ let json = {};
14589
+ try {
14590
+ json = JSON.parse(r2.body);
14591
+ } catch (_) {
14592
+ }
14593
+ if (r2.status !== 200)
14594
+ return { error: json.error || `Server error ${r2.status}` };
14595
+ const text = json.text ?? "";
14596
+ if (!text)
14597
+ return { error: "web_search succeeded but returned no results text." };
14598
+ return { output: text };
14599
+ } catch (err) {
14600
+ return { error: `web_search failed: ${err.message}` };
14601
+ }
14602
+ }
14513
14603
  function memoryScopeOf(input) {
14514
14604
  return input.scope === "global" ? "global" : "project";
14515
14605
  }
@@ -14605,6 +14695,7 @@ ${expanded}` };
14605
14695
  move_file: moveFile,
14606
14696
  delete_file: deleteFile,
14607
14697
  fetch_url: fetchUrl,
14698
+ web_search: webSearch,
14608
14699
  generate_image: generateImage,
14609
14700
  stock_photo: stockPhoto,
14610
14701
  todo_write: todoWrite,
@@ -66054,20 +66145,6 @@ ${content}
66054
66145
  return;
66055
66146
  }
66056
66147
  const model = normaliseModelId(modelAlias);
66057
- if (isPdf && NO_PDF_MODELS.has(model)) {
66058
- console.log(source_default.red(
66059
- ` ${resolveModelLabel(modelAlias)} does not support PDF attachments. Switch models with /model (Claude models support PDFs), or extract the text yourself and use /add instead.`
66060
- ));
66061
- rl.prompt();
66062
- return;
66063
- }
66064
- if (!isPdf && NO_VISION_MODELS.has(model)) {
66065
- console.log(source_default.red(
66066
- ` ${resolveModelLabel(modelAlias)} does not support image input. Switch models with /model, or attach as text with /add instead.`
66067
- ));
66068
- rl.prompt();
66069
- return;
66070
- }
66071
66148
  let buf;
66072
66149
  try {
66073
66150
  buf = fs8.readFileSync(filePath);
@@ -66087,9 +66164,24 @@ ${content}
66087
66164
  const label = caption ? `[Attached: ${rel}]
66088
66165
  ${caption}` : `[Attached: ${rel}]`;
66089
66166
  const content = [{ type: "text", text: label }];
66090
- content.push(
66091
- isPdf ? { type: "document", source: { type: "base64", media_type: "application/pdf", data } } : { type: "image", source: { type: "base64", media_type: mimeType, data } }
66092
- );
66167
+ const needsSidecar = isPdf ? NO_PDF_MODELS.has(model) : NO_VISION_MODELS.has(model);
66168
+ if (needsSidecar) {
66169
+ console.log(source_default.dim(` ${resolveModelLabel(modelAlias)} can't read ${isPdf ? "PDFs" : "images"} directly \u2014 describing ${rel} with Claude Sonnet 5 first\u2026`));
66170
+ try {
66171
+ const { text } = await (0, import_code_core3.describeAttachment)(isPdf ? "pdf" : "image", data, mimeType, rel);
66172
+ content.push({ type: "text", text: `<attachment_description>
66173
+ ${text}
66174
+ </attachment_description>` });
66175
+ } catch (err) {
66176
+ console.log(source_default.red(` Could not describe ${rel}: ${err.message}`));
66177
+ rl.prompt();
66178
+ return;
66179
+ }
66180
+ } else {
66181
+ content.push(
66182
+ isPdf ? { type: "document", source: { type: "base64", media_type: "application/pdf", data } } : { type: "image", source: { type: "base64", media_type: mimeType, data } }
66183
+ );
66184
+ }
66093
66185
  console.log(source_default.dim(` Attached ${rel} (${(buf.length / 1024).toFixed(0)}KB) \u2014 sending\u2026`));
66094
66186
  checkpoints.beginTurn(`/image ${rel}${caption ? " " + caption : ""}`, messages.length);
66095
66187
  messages.push({ role: "user", content });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexrall-code",
3
- "version": "0.5.72",
3
+ "version": "0.5.74",
4
4
  "description": "Nexrall Code — AI coding assistant for your terminal (headless agent for scripts, CI and automation)",
5
5
  "keywords": [
6
6
  "ai",
@@ -39,7 +39,7 @@
39
39
  "release": "node build.js && node scripts/upload-release.cjs"
40
40
  },
41
41
  "dependencies": {
42
- "@nexrall/code-core": "^1.4.42",
42
+ "@nexrall/code-core": "^1.4.44",
43
43
  "chalk": "^5.3.0",
44
44
  "commander": "^12.0.0",
45
45
  "diff": "^5.2.0",