nex-code 0.3.4 → 0.3.7
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/README.md +34 -12
- package/dist/bundle.js +505 -0
- package/dist/nex-code.js +485 -0
- package/package.json +8 -6
- package/bin/nex-code.js +0 -99
- package/cli/agent.js +0 -835
- package/cli/compactor.js +0 -85
- package/cli/context-engine.js +0 -507
- package/cli/context.js +0 -98
- package/cli/costs.js +0 -290
- package/cli/diff.js +0 -366
- package/cli/file-history.js +0 -94
- package/cli/format.js +0 -211
- package/cli/fuzzy-match.js +0 -270
- package/cli/git.js +0 -211
- package/cli/hooks.js +0 -173
- package/cli/index.js +0 -1289
- package/cli/mcp.js +0 -284
- package/cli/memory.js +0 -170
- package/cli/ollama.js +0 -130
- package/cli/permissions.js +0 -124
- package/cli/picker.js +0 -201
- package/cli/planner.js +0 -282
- package/cli/providers/anthropic.js +0 -333
- package/cli/providers/base.js +0 -116
- package/cli/providers/gemini.js +0 -239
- package/cli/providers/local.js +0 -249
- package/cli/providers/ollama.js +0 -228
- package/cli/providers/openai.js +0 -237
- package/cli/providers/registry.js +0 -454
- package/cli/render.js +0 -495
- package/cli/safety.js +0 -241
- package/cli/session.js +0 -133
- package/cli/skills.js +0 -412
- package/cli/spinner.js +0 -371
- package/cli/sub-agent.js +0 -425
- package/cli/tasks.js +0 -179
- package/cli/tool-tiers.js +0 -164
- package/cli/tool-validator.js +0 -138
- package/cli/tools.js +0 -1050
- package/cli/ui.js +0 -93
package/cli/git.js
DELETED
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* cli/git.js — Git Intelligence
|
|
3
|
-
* Smart commits, PR creation, branch management, diff-aware context
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const { execSync, execFileSync } = require('child_process');
|
|
7
|
-
const { C } = require('./ui');
|
|
8
|
-
|
|
9
|
-
function exec(cmd) {
|
|
10
|
-
try {
|
|
11
|
-
return execSync(cmd, { cwd: process.cwd(), encoding: 'utf-8', timeout: 30000, stdio: 'pipe' }).trim();
|
|
12
|
-
} catch (e) {
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function execGit(...args) {
|
|
18
|
-
try {
|
|
19
|
-
return execFileSync('git', args, { cwd: process.cwd(), encoding: 'utf-8', timeout: 30000, stdio: 'pipe' }).trim();
|
|
20
|
-
} catch (e) {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Check if we're in a git repository
|
|
27
|
-
*/
|
|
28
|
-
function isGitRepo() {
|
|
29
|
-
return exec('git rev-parse --is-inside-work-tree') === 'true';
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Get current branch name
|
|
34
|
-
*/
|
|
35
|
-
function getCurrentBranch() {
|
|
36
|
-
return exec('git branch --show-current');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Get git status (short format)
|
|
41
|
-
*/
|
|
42
|
-
function getStatus() {
|
|
43
|
-
try {
|
|
44
|
-
const raw = execSync('git status --porcelain', { cwd: process.cwd(), encoding: 'utf-8', timeout: 30000, stdio: 'pipe' });
|
|
45
|
-
if (!raw || !raw.trim()) return [];
|
|
46
|
-
return raw.split('\n').filter(Boolean).map((line) => {
|
|
47
|
-
const status = line.substring(0, 2).trim();
|
|
48
|
-
const file = line.substring(3);
|
|
49
|
-
return { status, file };
|
|
50
|
-
});
|
|
51
|
-
} catch {
|
|
52
|
-
return [];
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Get the diff for staged + unstaged changes
|
|
58
|
-
* @param {boolean} staged — only staged changes
|
|
59
|
-
*/
|
|
60
|
-
function getDiff(staged = false) {
|
|
61
|
-
const flag = staged ? '--cached' : '';
|
|
62
|
-
return exec(`git diff ${flag}`) || '';
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Get list of changed files (staged + unstaged)
|
|
67
|
-
*/
|
|
68
|
-
function getChangedFiles() {
|
|
69
|
-
const status = getStatus();
|
|
70
|
-
return status.map((s) => s.file);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Analyze diff and generate a commit message suggestion
|
|
75
|
-
* @returns {{ summary: string, files: string[], stats: { additions: number, deletions: number } }}
|
|
76
|
-
*/
|
|
77
|
-
function analyzeDiff() {
|
|
78
|
-
const files = getChangedFiles();
|
|
79
|
-
if (files.length === 0) return null;
|
|
80
|
-
|
|
81
|
-
const diff = getDiff();
|
|
82
|
-
const stagedDiff = getDiff(true);
|
|
83
|
-
const fullDiff = stagedDiff || diff;
|
|
84
|
-
|
|
85
|
-
// Count additions/deletions from diff (if available)
|
|
86
|
-
let additions = 0;
|
|
87
|
-
let deletions = 0;
|
|
88
|
-
if (fullDiff) {
|
|
89
|
-
const lines = fullDiff.split('\n');
|
|
90
|
-
for (const line of lines) {
|
|
91
|
-
if (line.startsWith('+') && !line.startsWith('+++')) additions++;
|
|
92
|
-
if (line.startsWith('-') && !line.startsWith('---')) deletions++;
|
|
93
|
-
}
|
|
94
|
-
} else {
|
|
95
|
-
// Untracked files: count as additions
|
|
96
|
-
additions = files.length;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Determine type based on files and content
|
|
100
|
-
let type = 'chore';
|
|
101
|
-
const fileNames = files.join(' ').toLowerCase();
|
|
102
|
-
if (fileNames.includes('test')) type = 'test';
|
|
103
|
-
else if (fileNames.includes('readme') || fileNames.includes('doc')) type = 'docs';
|
|
104
|
-
else if (additions > deletions * 2) type = 'feat';
|
|
105
|
-
else if (deletions > additions) type = 'refactor';
|
|
106
|
-
else type = 'fix';
|
|
107
|
-
|
|
108
|
-
// Generate summary from file names
|
|
109
|
-
const shortFiles = files.slice(0, 3).map((f) => f.split('/').pop());
|
|
110
|
-
const summary = `${type}: update ${shortFiles.join(', ')}${files.length > 3 ? ` (+${files.length - 3} more)` : ''}`;
|
|
111
|
-
|
|
112
|
-
return {
|
|
113
|
-
summary,
|
|
114
|
-
type,
|
|
115
|
-
files,
|
|
116
|
-
stats: { additions, deletions },
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Create a branch from a task description
|
|
122
|
-
* @param {string} description
|
|
123
|
-
* @returns {string|null} branch name or null on error
|
|
124
|
-
*/
|
|
125
|
-
function createBranch(description) {
|
|
126
|
-
const name = description
|
|
127
|
-
.toLowerCase()
|
|
128
|
-
.replace(/[^a-z0-9\s-]/g, '')
|
|
129
|
-
.replace(/\s+/g, '-')
|
|
130
|
-
.substring(0, 50);
|
|
131
|
-
|
|
132
|
-
const branchName = `feat/${name}`;
|
|
133
|
-
const result = execGit('checkout', '-b', branchName);
|
|
134
|
-
return result !== null ? branchName : null;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Stage all changes and commit
|
|
139
|
-
* @param {string} message
|
|
140
|
-
* @returns {string|null} commit hash or null
|
|
141
|
-
*/
|
|
142
|
-
function commit(message) {
|
|
143
|
-
execGit('add', '-A');
|
|
144
|
-
const result = execGit('commit', '-m', message);
|
|
145
|
-
if (!result) return null;
|
|
146
|
-
return execGit('rev-parse', '--short', 'HEAD');
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Show a formatted diff summary
|
|
151
|
-
*/
|
|
152
|
-
function formatDiffSummary() {
|
|
153
|
-
const analysis = analyzeDiff();
|
|
154
|
-
if (!analysis) return `${C.dim}No changes${C.reset}`;
|
|
155
|
-
|
|
156
|
-
const lines = [];
|
|
157
|
-
lines.push(`\n${C.bold}${C.cyan}Git Diff Summary:${C.reset}`);
|
|
158
|
-
lines.push(` ${C.green}+${analysis.stats.additions}${C.reset} ${C.red}-${analysis.stats.deletions}${C.reset} in ${analysis.files.length} file(s)`);
|
|
159
|
-
lines.push(`\n${C.bold}${C.cyan}Files:${C.reset}`);
|
|
160
|
-
for (const f of analysis.files.slice(0, 20)) {
|
|
161
|
-
lines.push(` ${C.dim}${f}${C.reset}`);
|
|
162
|
-
}
|
|
163
|
-
if (analysis.files.length > 20) {
|
|
164
|
-
lines.push(` ${C.dim}...+${analysis.files.length - 20} more${C.reset}`);
|
|
165
|
-
}
|
|
166
|
-
lines.push(`\n${C.bold}${C.cyan}Suggested message:${C.reset}`);
|
|
167
|
-
lines.push(` ${C.cyan}${analysis.summary}${C.reset}\n`);
|
|
168
|
-
return lines.join('\n');
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Get files with unresolved merge conflicts (UU, AA, DD)
|
|
173
|
-
*/
|
|
174
|
-
function getMergeConflicts() {
|
|
175
|
-
const status = getStatus();
|
|
176
|
-
return status.filter(s => s.status === 'UU' || s.status === 'AA' || s.status === 'DD');
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Get diff-aware context (only changed files' content)
|
|
181
|
-
* For use when the user is working on git-related tasks
|
|
182
|
-
*/
|
|
183
|
-
function getDiffContext() {
|
|
184
|
-
const files = getChangedFiles();
|
|
185
|
-
if (files.length === 0) return '';
|
|
186
|
-
|
|
187
|
-
const parts = [`CHANGED FILES (${files.length}):`];
|
|
188
|
-
for (const f of files.slice(0, 10)) {
|
|
189
|
-
parts.push(` ${f}`);
|
|
190
|
-
}
|
|
191
|
-
const diff = getDiff();
|
|
192
|
-
if (diff) {
|
|
193
|
-
const truncated = diff.length > 5000 ? diff.substring(0, 5000) + '\n...(truncated)' : diff;
|
|
194
|
-
parts.push(`\nDIFF:\n${truncated}`);
|
|
195
|
-
}
|
|
196
|
-
return parts.join('\n');
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
module.exports = {
|
|
200
|
-
isGitRepo,
|
|
201
|
-
getCurrentBranch,
|
|
202
|
-
getStatus,
|
|
203
|
-
getDiff,
|
|
204
|
-
getChangedFiles,
|
|
205
|
-
analyzeDiff,
|
|
206
|
-
createBranch,
|
|
207
|
-
commit,
|
|
208
|
-
formatDiffSummary,
|
|
209
|
-
getDiffContext,
|
|
210
|
-
getMergeConflicts,
|
|
211
|
-
};
|
package/cli/hooks.js
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* cli/hooks.js — Hook System
|
|
3
|
-
* Execute custom scripts in response to CLI events.
|
|
4
|
-
* Hook scripts live in .nex/hooks/ or are configured in .nex/config.json
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const { execSync } = require('child_process');
|
|
8
|
-
const path = require('path');
|
|
9
|
-
const fs = require('fs');
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Valid hook events
|
|
13
|
-
*/
|
|
14
|
-
const HOOK_EVENTS = [
|
|
15
|
-
'pre-tool', // Before any tool execution
|
|
16
|
-
'post-tool', // After tool execution
|
|
17
|
-
'pre-commit', // Before git commit
|
|
18
|
-
'post-response', // After LLM response
|
|
19
|
-
'session-start', // When REPL starts
|
|
20
|
-
'session-end', // When REPL exits
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
function getHooksDir() {
|
|
24
|
-
return path.join(process.cwd(), '.nex', 'hooks');
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function getConfigPath() {
|
|
28
|
-
return path.join(process.cwd(), '.nex', 'config.json');
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Load hook configuration from .nex/config.json
|
|
33
|
-
* @returns {Object<string, string[]>} — event → array of commands
|
|
34
|
-
*/
|
|
35
|
-
function loadHookConfig() {
|
|
36
|
-
const configPath = getConfigPath();
|
|
37
|
-
if (!fs.existsSync(configPath)) return {};
|
|
38
|
-
try {
|
|
39
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
40
|
-
return config.hooks || {};
|
|
41
|
-
} catch {
|
|
42
|
-
return {};
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Get all hooks for a given event
|
|
48
|
-
* Sources: .nex/hooks/{event} files + .nex/config.json hooks
|
|
49
|
-
* @param {string} event
|
|
50
|
-
* @returns {string[]} — array of commands to execute
|
|
51
|
-
*/
|
|
52
|
-
function getHooksForEvent(event) {
|
|
53
|
-
if (!HOOK_EVENTS.includes(event)) return [];
|
|
54
|
-
|
|
55
|
-
const hooks = [];
|
|
56
|
-
|
|
57
|
-
// 1. Check .nex/hooks/{event} script
|
|
58
|
-
const hooksDir = getHooksDir();
|
|
59
|
-
const scriptPath = path.join(hooksDir, event);
|
|
60
|
-
if (fs.existsSync(scriptPath)) {
|
|
61
|
-
hooks.push(scriptPath);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// 2. Check .nex/config.json hooks
|
|
65
|
-
const config = loadHookConfig();
|
|
66
|
-
if (config[event]) {
|
|
67
|
-
const cmds = Array.isArray(config[event]) ? config[event] : [config[event]];
|
|
68
|
-
hooks.push(...cmds);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return hooks;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Execute a single hook command
|
|
76
|
-
* @param {string} command
|
|
77
|
-
* @param {Object} env — additional environment variables
|
|
78
|
-
* @param {number} timeout — ms
|
|
79
|
-
* @returns {{success: boolean, output?: string, error?: string}}
|
|
80
|
-
*/
|
|
81
|
-
function executeHook(command, env = {}, timeout = 30000) {
|
|
82
|
-
try {
|
|
83
|
-
const output = execSync(command, {
|
|
84
|
-
cwd: process.cwd(),
|
|
85
|
-
encoding: 'utf-8',
|
|
86
|
-
timeout,
|
|
87
|
-
env: { ...process.env, ...env },
|
|
88
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
89
|
-
});
|
|
90
|
-
return { success: true, output: output.trim() };
|
|
91
|
-
} catch (err) {
|
|
92
|
-
return {
|
|
93
|
-
success: false,
|
|
94
|
-
error: err.stderr ? err.stderr.trim() : err.message,
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Run all hooks for an event
|
|
101
|
-
* @param {string} event
|
|
102
|
-
* @param {Object} context — contextual data passed as env vars
|
|
103
|
-
* @returns {Array<{command: string, success: boolean, output?: string, error?: string}>}
|
|
104
|
-
*/
|
|
105
|
-
function runHooks(event, context = {}) {
|
|
106
|
-
const hooks = getHooksForEvent(event);
|
|
107
|
-
if (hooks.length === 0) return [];
|
|
108
|
-
|
|
109
|
-
// Convert context to NEX_* env vars
|
|
110
|
-
const env = {};
|
|
111
|
-
for (const [key, value] of Object.entries(context)) {
|
|
112
|
-
env[`NEX_${key.toUpperCase()}`] = String(value);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const results = [];
|
|
116
|
-
for (const command of hooks) {
|
|
117
|
-
const result = executeHook(command, env);
|
|
118
|
-
results.push({ command, ...result });
|
|
119
|
-
|
|
120
|
-
// Stop on failure for pre-* hooks (they can block)
|
|
121
|
-
if (!result.success && event.startsWith('pre-')) {
|
|
122
|
-
break;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return results;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Check if any hooks are configured for an event
|
|
131
|
-
* @param {string} event
|
|
132
|
-
* @returns {boolean}
|
|
133
|
-
*/
|
|
134
|
-
function hasHooks(event) {
|
|
135
|
-
return getHooksForEvent(event).length > 0;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* List all configured hooks
|
|
140
|
-
* @returns {Array<{event: string, commands: string[]}>}
|
|
141
|
-
*/
|
|
142
|
-
function listHooks() {
|
|
143
|
-
const result = [];
|
|
144
|
-
for (const event of HOOK_EVENTS) {
|
|
145
|
-
const commands = getHooksForEvent(event);
|
|
146
|
-
if (commands.length > 0) {
|
|
147
|
-
result.push({ event, commands });
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
return result;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Initialize hooks directory
|
|
155
|
-
*/
|
|
156
|
-
function initHooksDir() {
|
|
157
|
-
const dir = getHooksDir();
|
|
158
|
-
if (!fs.existsSync(dir)) {
|
|
159
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
160
|
-
}
|
|
161
|
-
return dir;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
module.exports = {
|
|
165
|
-
HOOK_EVENTS,
|
|
166
|
-
loadHookConfig,
|
|
167
|
-
getHooksForEvent,
|
|
168
|
-
executeHook,
|
|
169
|
-
runHooks,
|
|
170
|
-
hasHooks,
|
|
171
|
-
listHooks,
|
|
172
|
-
initHooksDir,
|
|
173
|
-
};
|