Skip to content

Commit 62e66e9

Browse files
thegreatalxxclaude
andcommitted
feat: VS Code extension (LocalCode — Nyx AI)
- localcode.open — open LocalCode in integrated terminal (Ctrl+Shift+L / Cmd+Shift+L) - localcode.askAboutSelection — ask Nyx about selected text (editor context menu) - localcode.askAboutFile — ask Nyx about current file (editor + explorer context menu) - localcode.explainSelection — explain selected code with Nyx - localcode.reviewChanges — run /review in LocalCode - localcode.runTests — run /test in LocalCode - Status bar ⬡ Nyx button always visible - Terminal reuse: Nyx terminal is reused across commands - Selection → temp file → /explain <path> (clipboard untouched) - Settings: localcode.command, localcode.useNpx (npx @localcode/cli mode) - Full README with command table, keybindings, config reference Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 37855ae commit 62e66e9

7 files changed

Lines changed: 755 additions & 0 deletions

File tree

vscode-extension/.vscodeignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.vscode/**
2+
src/**
3+
tsconfig.json
4+
node_modules/**
5+
out/maps/**
6+
**/*.map
7+
.gitignore

vscode-extension/README.md

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# LocalCode — Nyx AI (VS Code Extension)
2+
3+
Bring **Nyx**, the LocalCode AI coding assistant, directly into VS Code. Every command opens LocalCode in the integrated terminal — no browser, no cloud lock-in, just your preferred model running locally or via API.
4+
5+
---
6+
7+
## Requirements
8+
9+
| Requirement | Details |
10+
|---|---|
11+
| Node.js | 18 or later |
12+
| LocalCode | `npm install -g @localcode/cli` **or** enable npx mode in settings |
13+
| VS Code | 1.85.0 or later |
14+
15+
---
16+
17+
## Installation
18+
19+
1. Install LocalCode globally (recommended):
20+
```bash
21+
npm install -g @localcode/cli
22+
```
23+
Or enable **npx mode** in the extension settings if you prefer not to install globally.
24+
25+
2. Install this extension from the VS Code Marketplace or by loading the `.vsix` file.
26+
27+
---
28+
29+
## Commands
30+
31+
All commands are available via the Command Palette (`Ctrl+Shift+P` / `Cmd+Shift+P`). Search for **LocalCode** to see the full list.
32+
33+
| Command | Description |
34+
|---|---|
35+
| `LocalCode: Open` | Open Nyx in the integrated terminal |
36+
| `LocalCode: Ask Nyx about selection` | Pass selected code to Nyx for analysis |
37+
| `LocalCode: Ask Nyx about this file` | Open Nyx in the current file's directory |
38+
| `LocalCode: Explain with Nyx` | Run `/explain` on the selected code (or full file) |
39+
| `LocalCode: Review changes` | Run `/review` to review pending git changes |
40+
| `LocalCode: Run tests with Nyx` | Run `/test` to generate or execute tests |
41+
42+
---
43+
44+
## Keybindings
45+
46+
| Shortcut | Action |
47+
|---|---|
48+
| `Ctrl+Shift+L` (Windows / Linux) | Open LocalCode |
49+
| `Cmd+Shift+L` (Mac) | Open LocalCode |
50+
51+
---
52+
53+
## Context Menus
54+
55+
### Editor right-click (on selected text)
56+
- **Ask Nyx about this** — sends selected code to Nyx
57+
- **Explain with Nyx** — runs `/explain` on the selection
58+
59+
### Editor right-click (no selection)
60+
- **Ask Nyx about this file** — opens Nyx in the file's folder
61+
62+
### Explorer right-click (on a file)
63+
- **Ask Nyx about this file** — opens Nyx in the file's folder
64+
65+
---
66+
67+
## Status Bar
68+
69+
A persistent **⬡ Nyx** button appears on the left side of the VS Code status bar. Click it at any time to open LocalCode.
70+
71+
---
72+
73+
## Configuration
74+
75+
Open VS Code Settings (`Ctrl+,` / `Cmd+,`) and search for **LocalCode**.
76+
77+
| Setting | Type | Default | Description |
78+
|---|---|---|---|
79+
| `localcode.command` | string | `"localcode"` | The shell command used to launch LocalCode. Change this if your binary is installed under a different name or path. |
80+
| `localcode.useNpx` | boolean | `false` | When enabled, LocalCode is launched via `npx @localcode/cli` instead of the globally installed command. Useful in environments where a global install is not possible. |
81+
82+
### Example: `settings.json`
83+
```json
84+
{
85+
"localcode.useNpx": true
86+
}
87+
```
88+
89+
---
90+
91+
## How it works
92+
93+
Each command opens (or reuses) an integrated terminal named **Nyx** and runs the LocalCode CLI in the relevant working directory. Slash commands (`/explain`, `/review`, `/test`, etc.) are sent to the running TUI automatically after a short initialisation delay.
94+
95+
When you invoke **Ask Nyx about selection** or **Explain with Nyx**, the selected code is written to a temporary file which is passed as an argument — keeping your clipboard untouched.
96+
97+
---
98+
99+
## LocalCode slash commands (reference)
100+
101+
LocalCode ships with 30+ slash commands. Some highlights:
102+
103+
| Command | Description |
104+
|---|---|
105+
| `/explain <file>` | Explain a file or snippet |
106+
| `/review` | Review staged/unstaged git changes |
107+
| `/test` | Generate or run tests |
108+
| `/commit` | Generate a commit message and commit |
109+
| `/diff` | Show and summarise a diff |
110+
| `/git` | Run git operations with AI guidance |
111+
| `/image` | Analyse an image |
112+
| `/watch` | Watch a file for changes |
113+
| `/share` | Share a snippet |
114+
115+
Run `/help` inside LocalCode to see the full list.
116+
117+
---
118+
119+
## License
120+
121+
MIT — see the [LocalCode repository](https://github.com/thealxlabs/localcode) for details.

vscode-extension/out/extension.js

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
"use strict";
2+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3+
if (k2 === undefined) k2 = k;
4+
var desc = Object.getOwnPropertyDescriptor(m, k);
5+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6+
desc = { enumerable: true, get: function() { return m[k]; } };
7+
}
8+
Object.defineProperty(o, k2, desc);
9+
}) : (function(o, m, k, k2) {
10+
if (k2 === undefined) k2 = k;
11+
o[k2] = m[k];
12+
}));
13+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14+
Object.defineProperty(o, "default", { enumerable: true, value: v });
15+
}) : function(o, v) {
16+
o["default"] = v;
17+
});
18+
var __importStar = (this && this.__importStar) || (function () {
19+
var ownKeys = function(o) {
20+
ownKeys = Object.getOwnPropertyNames || function (o) {
21+
var ar = [];
22+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23+
return ar;
24+
};
25+
return ownKeys(o);
26+
};
27+
return function (mod) {
28+
if (mod && mod.__esModule) return mod;
29+
var result = {};
30+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31+
__setModuleDefault(result, mod);
32+
return result;
33+
};
34+
})();
35+
Object.defineProperty(exports, "__esModule", { value: true });
36+
exports.activate = activate;
37+
exports.deactivate = deactivate;
38+
const vscode = __importStar(require("vscode"));
39+
const path = __importStar(require("path"));
40+
const fs = __importStar(require("fs"));
41+
const os = __importStar(require("os"));
42+
// ---------------------------------------------------------------------------
43+
// Helpers
44+
// ---------------------------------------------------------------------------
45+
/** Return the shell command used to launch LocalCode. */
46+
function getLaunchCommand() {
47+
const config = vscode.workspace.getConfiguration('localcode');
48+
const useNpx = config.get('useNpx', false);
49+
if (useNpx) {
50+
return 'npx @localcode/cli';
51+
}
52+
return config.get('command', 'localcode');
53+
}
54+
/**
55+
* Return the best working directory for the terminal.
56+
* Priority: active editor file's folder → first workspace folder → home dir.
57+
*/
58+
function getWorkingDirectory(uri) {
59+
if (uri) {
60+
const stat = fs.statSync(uri.fsPath, { throwIfNoEntry: false });
61+
if (stat) {
62+
return stat.isDirectory() ? uri.fsPath : path.dirname(uri.fsPath);
63+
}
64+
}
65+
const editor = vscode.window.activeTextEditor;
66+
if (editor && !editor.document.isUntitled) {
67+
return path.dirname(editor.document.uri.fsPath);
68+
}
69+
const folders = vscode.workspace.workspaceFolders;
70+
if (folders && folders.length > 0) {
71+
return folders[0].uri.fsPath;
72+
}
73+
return os.homedir();
74+
}
75+
/**
76+
* Find an existing "Nyx" terminal or create a new one.
77+
* If `forceNew` is true a fresh terminal is always created (useful when the
78+
* cwd must differ from the existing one).
79+
*/
80+
function getOrCreateTerminal(cwd, forceNew = false) {
81+
if (!forceNew) {
82+
const existing = vscode.window.terminals.find((t) => t.name === 'Nyx');
83+
if (existing) {
84+
return existing;
85+
}
86+
}
87+
return vscode.window.createTerminal({
88+
name: 'Nyx',
89+
cwd,
90+
});
91+
}
92+
/**
93+
* Open a Nyx terminal in `cwd`, start LocalCode, then optionally send a
94+
* follow-up command string once the process is running.
95+
*
96+
* Because LocalCode is an interactive TUI we cannot reliably detect when it
97+
* has fully started before sending text. A small delay of ~600 ms is the
98+
* pragmatic solution used by many terminal-driving extensions.
99+
*/
100+
async function openLocalCode(cwd, followUpCommand, forceNew = false) {
101+
const cmd = getLaunchCommand();
102+
const terminal = getOrCreateTerminal(cwd, forceNew);
103+
terminal.show(true);
104+
if (followUpCommand) {
105+
// Start LocalCode then send the slash command after a short delay so the
106+
// TUI has time to initialise before receiving input.
107+
terminal.sendText(cmd);
108+
await delay(650);
109+
terminal.sendText(followUpCommand);
110+
}
111+
else {
112+
terminal.sendText(cmd);
113+
}
114+
}
115+
/** Write text to a temp file and return its path. */
116+
function writeTempFile(content, ext = '.txt') {
117+
const tmpPath = path.join(os.tmpdir(), `nyx-context-${Date.now()}${ext}`);
118+
fs.writeFileSync(tmpPath, content, 'utf8');
119+
return tmpPath;
120+
}
121+
function delay(ms) {
122+
return new Promise((resolve) => setTimeout(resolve, ms));
123+
}
124+
// ---------------------------------------------------------------------------
125+
// Extension entry points
126+
// ---------------------------------------------------------------------------
127+
function activate(context) {
128+
// ── 1. localcode.open ────────────────────────────────────────────────────
129+
const openCmd = vscode.commands.registerCommand('localcode.open', async () => {
130+
const cwd = getWorkingDirectory();
131+
await openLocalCode(cwd);
132+
});
133+
// ── 2. localcode.askAboutSelection ───────────────────────────────────────
134+
const askAboutSelectionCmd = vscode.commands.registerCommand('localcode.askAboutSelection', async () => {
135+
const editor = vscode.window.activeTextEditor;
136+
if (!editor) {
137+
vscode.window.showWarningMessage('LocalCode: No active editor found.');
138+
return;
139+
}
140+
const selection = editor.selection;
141+
if (selection.isEmpty) {
142+
vscode.window.showWarningMessage('LocalCode: No text selected.');
143+
return;
144+
}
145+
const selectedText = editor.document.getText(selection);
146+
const fileExt = path.extname(editor.document.fileName) || '.txt';
147+
// Write the selected snippet to a temp file so LocalCode can read it.
148+
const tmpFile = writeTempFile(selectedText, fileExt);
149+
const cwd = getWorkingDirectory();
150+
// Open LocalCode and send a /explain command pointing at the temp file.
151+
// This gives users immediate context without having to type anything.
152+
await openLocalCode(cwd, `/explain ${tmpFile}`, false);
153+
});
154+
// ── 3. localcode.askAboutFile ─────────────────────────────────────────────
155+
const askAboutFileCmd = vscode.commands.registerCommand('localcode.askAboutFile', async (uri) => {
156+
// `uri` is provided when triggered from the explorer context menu.
157+
// Fall back to the active editor when triggered from the editor context menu.
158+
const filePath = uri
159+
? uri.fsPath
160+
: vscode.window.activeTextEditor?.document.uri.fsPath;
161+
if (!filePath) {
162+
vscode.window.showWarningMessage('LocalCode: No file is currently open.');
163+
return;
164+
}
165+
const cwd = path.dirname(filePath);
166+
// Force a new terminal so cwd is set to the file's directory.
167+
await openLocalCode(cwd, undefined, true);
168+
});
169+
// ── 4. localcode.explainSelection ────────────────────────────────────────
170+
const explainSelectionCmd = vscode.commands.registerCommand('localcode.explainSelection', async () => {
171+
const editor = vscode.window.activeTextEditor;
172+
if (!editor) {
173+
vscode.window.showWarningMessage('LocalCode: No active editor found.');
174+
return;
175+
}
176+
const filePath = editor.document.uri.fsPath;
177+
const selection = editor.selection;
178+
const cwd = getWorkingDirectory();
179+
if (!selection.isEmpty) {
180+
// Write selection to a temp file and run /explain on it.
181+
const selectedText = editor.document.getText(selection);
182+
const fileExt = path.extname(filePath) || '.txt';
183+
const tmpFile = writeTempFile(selectedText, fileExt);
184+
await openLocalCode(cwd, `/explain ${tmpFile}`, false);
185+
}
186+
else {
187+
// No selection — explain the whole file.
188+
await openLocalCode(cwd, `/explain ${filePath}`, false);
189+
}
190+
});
191+
// ── 5. localcode.reviewChanges ────────────────────────────────────────────
192+
const reviewChangesCmd = vscode.commands.registerCommand('localcode.reviewChanges', async () => {
193+
const cwd = getWorkingDirectory();
194+
await openLocalCode(cwd, '/review', false);
195+
});
196+
// ── 6. localcode.runTests ─────────────────────────────────────────────────
197+
const runTestsCmd = vscode.commands.registerCommand('localcode.runTests', async () => {
198+
const cwd = getWorkingDirectory();
199+
await openLocalCode(cwd, '/test', false);
200+
});
201+
// ── Status bar item ───────────────────────────────────────────────────────
202+
const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100);
203+
statusBarItem.text = '⬡ Nyx';
204+
statusBarItem.tooltip = 'Open LocalCode — Nyx AI assistant';
205+
statusBarItem.command = 'localcode.open';
206+
statusBarItem.show();
207+
// ── Register all disposables ──────────────────────────────────────────────
208+
context.subscriptions.push(openCmd, askAboutSelectionCmd, askAboutFileCmd, explainSelectionCmd, reviewChangesCmd, runTestsCmd, statusBarItem);
209+
}
210+
function deactivate() {
211+
// Nothing to clean up — VS Code disposes subscriptions automatically.
212+
}

0 commit comments

Comments
 (0)