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.
- package/dist/index.js +111 -19
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
66091
|
-
|
|
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.
|
|
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
|
+
"@nexrall/code-core": "^1.4.44",
|
|
43
43
|
"chalk": "^5.3.0",
|
|
44
44
|
"commander": "^12.0.0",
|
|
45
45
|
"diff": "^5.2.0",
|