nexrall-code 0.5.115 → 0.5.117
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 +309 -103
- package/package.json +11 -16
- package/dist/audit.js +0 -102
- package/dist/commands/auth.js +0 -134
- package/dist/commands/chat.js +0 -2715
- package/dist/commands/plugin.js +0 -393
- package/dist/commands/sessions.js +0 -62
- package/dist/commands/update.js +0 -174
- package/dist/permissions/handler.js +0 -332
- package/dist/terminal/terminalSetup.js +0 -395
- package/dist/trust.js +0 -137
- package/dist/ui/diff.js +0 -51
- package/dist/ui/inkReadlineAdapter.js +0 -116
- package/dist/ui/inkTerminal.js +0 -1097
- package/dist/ui/resizeRepaint.js +0 -36
- package/dist/ui/screen.js +0 -482
- package/dist/ui/theme.js +0 -385
- package/dist/ui/trustPrompt.js +0 -126
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexrall-code",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.117",
|
|
4
4
|
"description": "Nexrall Code — AI coding assistant for your terminal (headless agent for scripts, CI and automation)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -12,11 +12,6 @@
|
|
|
12
12
|
"ci"
|
|
13
13
|
],
|
|
14
14
|
"homepage": "https://nexrall.com",
|
|
15
|
-
"repository": {
|
|
16
|
-
"type": "git",
|
|
17
|
-
"url": "git+https://github.com/nexrall/nexrall-code.git",
|
|
18
|
-
"directory": "packages/cli"
|
|
19
|
-
},
|
|
20
15
|
"license": "MIT",
|
|
21
16
|
"type": "module",
|
|
22
17
|
"bin": {
|
|
@@ -33,13 +28,6 @@
|
|
|
33
28
|
"engines": {
|
|
34
29
|
"node": ">=18"
|
|
35
30
|
},
|
|
36
|
-
"scripts": {
|
|
37
|
-
"build": "node build.js",
|
|
38
|
-
"dev": "tsx src/index.ts",
|
|
39
|
-
"start": "node dist/index.js",
|
|
40
|
-
"test": "tsc --noEmit && tsc --noEmit -p test/tsconfig.json && node --import tsx --test test/*.test.ts test/*.test.mts",
|
|
41
|
-
"release": "node build.js && node scripts/upload-release.cjs"
|
|
42
|
-
},
|
|
43
31
|
"dependencies": {
|
|
44
32
|
"chalk": "^5.3.0",
|
|
45
33
|
"commander": "^12.0.0",
|
|
@@ -53,13 +41,20 @@
|
|
|
53
41
|
},
|
|
54
42
|
"devDependencies": {
|
|
55
43
|
"@aws-sdk/client-s3": "^3.600.0",
|
|
56
|
-
"@nexrall/code-core": "workspace:*",
|
|
57
44
|
"@types/diff": "^5.2.1",
|
|
58
45
|
"@types/node": "^20.0.0",
|
|
59
46
|
"@types/prompts": "^2.4.9",
|
|
60
47
|
"@types/react": "^19.2.18",
|
|
61
48
|
"esbuild": "^0.20.0",
|
|
62
49
|
"tsx": "^4.7.0",
|
|
63
|
-
"typescript": "^6.0.3"
|
|
50
|
+
"typescript": "^6.0.3",
|
|
51
|
+
"@nexrall/code-core": "1.4.65"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "node build.js",
|
|
55
|
+
"dev": "tsx src/index.ts",
|
|
56
|
+
"start": "node dist/index.js",
|
|
57
|
+
"test": "tsc --noEmit && tsc --noEmit -p test/tsconfig.json && node --import tsx --test test/*.test.ts test/*.test.mts",
|
|
58
|
+
"release": "node build.js && node scripts/upload-release.cjs"
|
|
64
59
|
}
|
|
65
|
-
}
|
|
60
|
+
}
|
package/dist/audit.js
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `--audit [path]` — the CLI's opt-in tool-call trail.
|
|
3
|
-
*
|
|
4
|
-
* WHY THE CLI HAS THIS AT ALL
|
|
5
|
-
*
|
|
6
|
-
* The trail was originally skipped here: an interactive terminal already has a
|
|
7
|
-
* human watching every permission prompt, so the human IS the oversight layer,
|
|
8
|
-
* and shipping a flag nobody asked for is just maintenance surface.
|
|
9
|
-
*
|
|
10
|
-
* Headless mode is what changes the answer. `--output-format json|stream-json`
|
|
11
|
-
* auto-approves every tool call (there is no TTY to ask on) and is meant for CI
|
|
12
|
-
* and scripted runs — i.e. an agent acting with nobody watching. That is where a
|
|
13
|
-
* trail is worth MOST, not least: without one there is no way to answer "what
|
|
14
|
-
* did last night's job actually touch?". The consumer that makes it useful also
|
|
15
|
-
* already exists, since stream-json output is machine-readable by design.
|
|
16
|
-
*
|
|
17
|
-
* Still OPT-IN, including in headless: writing an unrequested file into a user's
|
|
18
|
-
* working directory is not a default worth taking, and every other host
|
|
19
|
-
* (code-core, the SDK, Desktop) keeps auditing off unless asked. Opt-in
|
|
20
|
-
* consistency is itself a property worth having.
|
|
21
|
-
*
|
|
22
|
-
* State lives at module level rather than being threaded through runTurn()'s
|
|
23
|
-
* parameter list, matching setMode()/setAutoApprove() in permissions/. That list
|
|
24
|
-
* already carries ten positional parameters across six call sites; an eleventh
|
|
25
|
-
* would have to be added correctly six times, and the failure mode of missing
|
|
26
|
-
* one is a silently unaudited turn — the exact defect this feature exists to
|
|
27
|
-
* prevent, and the one already hit once in @nexrall/agent.
|
|
28
|
-
*
|
|
29
|
-
* See docs/NEXRALL_AUDIT_TRAIL_LAYER.md in the Nexrall repo.
|
|
30
|
-
*/
|
|
31
|
-
import * as path from 'path';
|
|
32
|
-
import { createJsonlAuditSink, loadAuth } from '@nexrall/code-core';
|
|
33
|
-
let _state;
|
|
34
|
-
/**
|
|
35
|
-
* One sink per resolved path.
|
|
36
|
-
*
|
|
37
|
-
* Cached because the default path is derived from the session id, so `/clear`
|
|
38
|
-
* legitimately starts a NEW trail file, while an explicit `--audit <path>`
|
|
39
|
-
* keeps appending to one. Re-creating a sink per turn would also re-run the
|
|
40
|
-
* mkdir bookkeeping on every call for no benefit.
|
|
41
|
-
*/
|
|
42
|
-
const _sinks = new Map();
|
|
43
|
-
/** Default trail location when `--audit` is given with no path. */
|
|
44
|
-
export function defaultAuditPath(workDir, sessionId) {
|
|
45
|
-
return path.join(workDir, '.nexrall', 'audit', `${sessionId}.jsonl`);
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Resolve where this session's trail is written.
|
|
49
|
-
*
|
|
50
|
-
* An explicit path is resolved against the CWD the user invoked from, not
|
|
51
|
-
* `workDir`: a CI job typically wants the trail in its artifacts directory,
|
|
52
|
-
* which is usually not the repo being worked on.
|
|
53
|
-
*/
|
|
54
|
-
export function resolveAuditPath(state) {
|
|
55
|
-
return state.filePath
|
|
56
|
-
? path.resolve(state.filePath)
|
|
57
|
-
: defaultAuditPath(state.workDir, state.getSessionId());
|
|
58
|
-
}
|
|
59
|
-
/** Enable auditing for this process. Returns the path used for the first session. */
|
|
60
|
-
export function enableAudit(opts) {
|
|
61
|
-
_state = opts;
|
|
62
|
-
return resolveAuditPath(opts);
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* The audit config for the CURRENT turn, or undefined when `--audit` was not
|
|
66
|
-
* passed.
|
|
67
|
-
*
|
|
68
|
-
* Built per call (cheap: a map lookup plus an object literal) so a session id
|
|
69
|
-
* changed by `/clear` or `/resume` is picked up on the very next turn.
|
|
70
|
-
*/
|
|
71
|
-
export function currentAudit() {
|
|
72
|
-
if (!_state)
|
|
73
|
-
return undefined;
|
|
74
|
-
const target = resolveAuditPath(_state);
|
|
75
|
-
let sink = _sinks.get(target);
|
|
76
|
-
if (!sink) {
|
|
77
|
-
sink = createJsonlAuditSink(target);
|
|
78
|
-
_sinks.set(target, sink);
|
|
79
|
-
}
|
|
80
|
-
const cfg = {
|
|
81
|
-
sink,
|
|
82
|
-
// Pinned per turn to the CLI's live session id, so a whole conversation —
|
|
83
|
-
// every turn, and every sub-agent it delegates to — shares one session in
|
|
84
|
-
// the trail. code-core mints an id per runAgentLoop() call and the REPL
|
|
85
|
-
// calls it once PER TURN, so leaving this unset would make each turn look
|
|
86
|
-
// like a separate session and break the link between a later write and the
|
|
87
|
-
// request that caused it. Same trap as @nexrall/agent's and Desktop's.
|
|
88
|
-
sessionId: _state.getSessionId(),
|
|
89
|
-
};
|
|
90
|
-
// The human half of dual attribution. Absent when the account has no email on
|
|
91
|
-
// record — never substituted with a placeholder, because a fabricated
|
|
92
|
-
// attribution in a compliance record is worse than a visibly missing one.
|
|
93
|
-
const email = loadAuth()?.email;
|
|
94
|
-
if (email)
|
|
95
|
-
cfg.userId = email;
|
|
96
|
-
return cfg;
|
|
97
|
-
}
|
|
98
|
-
/** Test seam — resets module state between cases. */
|
|
99
|
-
export function resetAudit() {
|
|
100
|
-
_state = undefined;
|
|
101
|
-
_sinks.clear();
|
|
102
|
-
}
|
package/dist/commands/auth.js
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import * as readline from 'readline';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import { login, saveAuth, clearAuth, isAuthenticated, getBalance, revokeRefreshToken } from '@nexrall/code-core';
|
|
4
|
-
// ─── Hidden Password Prompt ───────────────────────────────────────────────────
|
|
5
|
-
function prompt(question, hidden = false) {
|
|
6
|
-
return new Promise((resolve) => {
|
|
7
|
-
const rl = readline.createInterface({
|
|
8
|
-
input: process.stdin,
|
|
9
|
-
output: process.stdout,
|
|
10
|
-
terminal: true,
|
|
11
|
-
});
|
|
12
|
-
if (hidden) {
|
|
13
|
-
// Temporarily suppress echoing for password input
|
|
14
|
-
process.stdout.write(question);
|
|
15
|
-
let value = '';
|
|
16
|
-
const { stdin } = process;
|
|
17
|
-
const wasRaw = stdin.isRaw;
|
|
18
|
-
stdin.setRawMode(true);
|
|
19
|
-
stdin.resume();
|
|
20
|
-
stdin.setEncoding('utf8');
|
|
21
|
-
const onData = (char) => {
|
|
22
|
-
if (char === '\n' || char === '\r' || char === '') {
|
|
23
|
-
// Enter or Ctrl+C
|
|
24
|
-
stdin.setRawMode(wasRaw ?? false);
|
|
25
|
-
stdin.pause();
|
|
26
|
-
stdin.removeListener('data', onData);
|
|
27
|
-
rl.close();
|
|
28
|
-
process.stdout.write('\n');
|
|
29
|
-
if (char === '') {
|
|
30
|
-
process.exit(0);
|
|
31
|
-
}
|
|
32
|
-
resolve(value);
|
|
33
|
-
}
|
|
34
|
-
else if (char === '' || char === '\b') {
|
|
35
|
-
// Backspace
|
|
36
|
-
if (value.length > 0) {
|
|
37
|
-
value = value.slice(0, -1);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
-
value += char;
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
stdin.on('data', onData);
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
rl.question(question, (answer) => {
|
|
48
|
-
rl.close();
|
|
49
|
-
resolve(answer.trim());
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
// ─── Auth Command ─────────────────────────────────────────────────────────────
|
|
55
|
-
export async function authCommand() {
|
|
56
|
-
console.log();
|
|
57
|
-
console.log(chalk.bold.blue(' Nexrall Code — Login'));
|
|
58
|
-
console.log(chalk.dim(' ─────────────────────'));
|
|
59
|
-
console.log();
|
|
60
|
-
if (isAuthenticated()) {
|
|
61
|
-
const overwrite = await prompt(chalk.yellow(' Already logged in. Login again? ') + chalk.dim('[y/n] > '));
|
|
62
|
-
if (overwrite !== 'y' && overwrite !== 'yes') {
|
|
63
|
-
console.log(chalk.dim(' Cancelled.'));
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
console.log();
|
|
67
|
-
}
|
|
68
|
-
const email = await prompt(chalk.bold(' Email ') + chalk.dim('> '));
|
|
69
|
-
const password = await prompt(chalk.bold(' Password ') + chalk.dim('> '), true);
|
|
70
|
-
console.log();
|
|
71
|
-
const spinner = startSpinner('Logging in…');
|
|
72
|
-
try {
|
|
73
|
-
const authConfig = await login(email, password);
|
|
74
|
-
saveAuth(authConfig);
|
|
75
|
-
stopSpinner(spinner);
|
|
76
|
-
console.log(chalk.green(' ✓ Logged in as ') + chalk.bold(authConfig.email ?? email));
|
|
77
|
-
console.log();
|
|
78
|
-
// Show balance
|
|
79
|
-
try {
|
|
80
|
-
const balance = await getBalance();
|
|
81
|
-
console.log(chalk.dim(' Wallet balance: ') + chalk.cyan(`$${balance.toFixed(4)}`));
|
|
82
|
-
}
|
|
83
|
-
catch {
|
|
84
|
-
// Non-fatal — balance fetch can fail
|
|
85
|
-
}
|
|
86
|
-
console.log();
|
|
87
|
-
console.log(chalk.dim(' Run ') + chalk.bold('nex') + chalk.dim(' to start coding.'));
|
|
88
|
-
console.log();
|
|
89
|
-
}
|
|
90
|
-
catch (err) {
|
|
91
|
-
stopSpinner(spinner);
|
|
92
|
-
console.log();
|
|
93
|
-
console.log(chalk.red(' ✗ Login failed: ') + String(err.message));
|
|
94
|
-
console.log();
|
|
95
|
-
process.exit(1);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
// ─── Logout Command ───────────────────────────────────────────────────────────
|
|
99
|
-
export async function logoutCommand() {
|
|
100
|
-
console.log();
|
|
101
|
-
if (!isAuthenticated()) {
|
|
102
|
-
console.log(chalk.dim(' Not logged in.'));
|
|
103
|
-
console.log();
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
const confirm = await prompt(chalk.yellow(' Log out? ') + chalk.dim('[y/n] > '));
|
|
107
|
-
if (confirm !== 'y' && confirm !== 'yes') {
|
|
108
|
-
console.log(chalk.dim(' Cancelled.'));
|
|
109
|
-
console.log();
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
// Revoke server-side BEFORE clearing the local file — the refresh token has to be
|
|
113
|
-
// read from it. Without this, "logging out" only deleted the local copy while the
|
|
114
|
-
// 60-day refresh token stayed valid and could keep minting access tokens for
|
|
115
|
-
// anyone who recovered it (a synced dotfile, a backup, a shared machine).
|
|
116
|
-
await revokeRefreshToken();
|
|
117
|
-
clearAuth();
|
|
118
|
-
console.log(chalk.green(' ✓ Logged out.'));
|
|
119
|
-
console.log();
|
|
120
|
-
}
|
|
121
|
-
function startSpinner(text) {
|
|
122
|
-
const frames = ['◐', '◓', '◑', '◒'];
|
|
123
|
-
let i = 0;
|
|
124
|
-
process.stdout.write(' ');
|
|
125
|
-
const interval = setInterval(() => {
|
|
126
|
-
process.stdout.write(`\r ${chalk.cyan(frames[i % frames.length])} ${chalk.dim(text)}`);
|
|
127
|
-
i++;
|
|
128
|
-
}, 120);
|
|
129
|
-
return { interval };
|
|
130
|
-
}
|
|
131
|
-
function stopSpinner(handle) {
|
|
132
|
-
clearInterval(handle.interval);
|
|
133
|
-
process.stdout.write('\r\x1b[K'); // Clear the spinner line
|
|
134
|
-
}
|