Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 34 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,15 +296,19 @@ Options:

### Custom Instructions

You can provide custom instructions to tailor Grok's behavior to your project by creating a `.grok/GROK.md` file in your project directory:
You can provide custom instructions to tailor Grok's behavior to your project or globally. Grok CLI supports both project-level and global custom instructions.

#### Project-Level Instructions

Create a `.grok/GROK.md` file in your project directory to provide instructions specific to that project:

```bash
mkdir .grok
```

Create `.grok/GROK.md` with your custom instructions:
Create `.grok/GROK.md` with your project-specific instructions:
```markdown
# Custom Instructions for Grok CLI
# Custom Instructions for This Project

Always use TypeScript for any new code files.
When creating React components, use functional components with hooks.
Expand All @@ -313,7 +317,33 @@ Always add JSDoc comments for public functions and interfaces.
Follow the existing code style and patterns in this project.
```

Grok will automatically load and follow these instructions when working in your project directory. The custom instructions are added to Grok's system prompt and take priority over default behavior.
#### Global Instructions

For instructions that apply across all projects, create `~/.grok/GROK.md` in your home directory:

```bash
mkdir -p ~/.grok
```

Create `~/.grok/GROK.md` with your global instructions:
```markdown
# Global Custom Instructions for Grok CLI

Always prioritize code readability and maintainability.
Use descriptive variable names and add comments for complex logic.
Follow best practices for the programming language being used.
When suggesting code changes, consider performance implications.
```

#### Priority Order

Grok will load custom instructions in the following priority order:
1. **Project-level** (`.grok/GROK.md` in current directory) - takes highest priority
2. **Global** (`~/.grok/GROK.md` in home directory) - fallback if no project instructions exist

If both files exist, project instructions will be used. If neither exists, Grok operates with its default behavior.

The custom instructions are added to Grok's system prompt and influence its responses across all interactions in the respective context.

## Morph Fast Apply (Optional)

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vibe-kit/grok-cli",
"version": "0.0.33",
"version": "0.0.34",
"description": "An open-source AI agent that brings the power of Grok directly into your terminal.",
"type": "module",
"main": "dist/index.js",
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ program

if (!apiKey) {
console.error(
"❌ Error: API key required. Set GROK_API_KEY environment variable, use --api-key flag, or save to ~/.grok/user-settings.json"
"❌ Error: API key required. Set GROK_API_KEY environment variable, use --api-key flag, or set \"apiKey\" field in ~/.grok/user-settings.json"
);
process.exit(1);
}
Expand Down
4 changes: 2 additions & 2 deletions src/tools/morph-editor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as fs from "fs-extra";
import fs from "fs-extra";
import * as path from "path";
import axios from "axios";
import { ToolResult } from "../types/index.js";
Expand Down Expand Up @@ -390,4 +390,4 @@ export class MorphEditorTool {
getApiKey(): string {
return this.morphApiKey;
}
}
}
2 changes: 1 addition & 1 deletion src/tools/search.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { spawn } from "child_process";
import { ToolResult } from "../types/index.js";
import { ConfirmationService } from "../utils/confirmation-service.js";
import * as fs from "fs-extra";
import fs from "fs-extra";
import * as path from "path";

export interface SearchResult {
Expand Down
154 changes: 95 additions & 59 deletions src/tools/text-editor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as fs from "fs-extra";
import fs from "fs-extra";
import * as path from "path";
import { writeFile as writeFilePromise } from "fs/promises";
import { ToolResult, EditorCommand } from "../types/index.js";
Expand Down Expand Up @@ -468,84 +468,120 @@ export class TextEditorTool {
const tokens = str.match(/\b(function|console\.log|return|if|else|for|while)\b/g) || [];
return tokens;
};

const searchTokens = extractTokens(search);
const actualTokens = extractTokens(actual);

if (searchTokens.length !== actualTokens.length) return false;

for (let i = 0; i < searchTokens.length; i++) {
if (searchTokens[i] !== actualTokens[i]) return false;
}

return true;
}

private generateDiff(
/**
* Compute Longest Common Subsequence using dynamic programming
* Returns array of indices in oldLines that are part of LCS
*/
private computeLCS(oldLines: string[], newLines: string[]): number[][] {
const m = oldLines.length;
const n = newLines.length;
const dp: number[][] = Array(m + 1).fill(0).map(() => Array(n + 1).fill(0));

// Build LCS length table
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (oldLines[i - 1] === newLines[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}

return dp;
}

/**
* Extract changes from LCS table
* Returns array of change regions
*/
private extractChanges(
oldLines: string[],
newLines: string[],
filePath: string
): string {
const CONTEXT_LINES = 3;

lcs: number[][]
): Array<{ oldStart: number; oldEnd: number; newStart: number; newEnd: number }> {
const changes: Array<{
oldStart: number;
oldEnd: number;
newStart: number;
newEnd: number;
}> = [];

let i = 0, j = 0;

while (i < oldLines.length || j < newLines.length) {
while (i < oldLines.length && j < newLines.length && oldLines[i] === newLines[j]) {
i++;
j++;
}

if (i < oldLines.length || j < newLines.length) {
const changeStart = { old: i, new: j };

let oldEnd = i;
let newEnd = j;

while (oldEnd < oldLines.length || newEnd < newLines.length) {
let matchFound = false;
let matchLength = 0;

for (let k = 0; k < Math.min(2, oldLines.length - oldEnd, newLines.length - newEnd); k++) {
if (oldEnd + k < oldLines.length &&
newEnd + k < newLines.length &&
oldLines[oldEnd + k] === newLines[newEnd + k]) {
matchLength++;
} else {
break;
}
}

if (matchLength >= 2 || (oldEnd >= oldLines.length && newEnd >= newLines.length)) {
matchFound = true;
}

if (matchFound) {
break;
}

if (oldEnd < oldLines.length) oldEnd++;
if (newEnd < newLines.length) newEnd++;

let i = oldLines.length;
let j = newLines.length;
let oldEnd = i;
let newEnd = j;
let inChange = false;

while (i > 0 || j > 0) {
if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) {
// Lines match - if we were in a change, close it
if (inChange) {
changes.unshift({
oldStart: i,
oldEnd: oldEnd,
newStart: j,
newEnd: newEnd
});
inChange = false;
}

changes.push({
oldStart: changeStart.old,
oldEnd: oldEnd,
newStart: changeStart.new,
newEnd: newEnd
});

i = oldEnd;
j = newEnd;
i--;
j--;
} else if (j > 0 && (i === 0 || lcs[i][j - 1] >= lcs[i - 1][j])) {
// Insertion in new file
if (!inChange) {
oldEnd = i;
newEnd = j;
inChange = true;
}
j--;
} else if (i > 0) {
// Deletion from old file
if (!inChange) {
oldEnd = i;
newEnd = j;
inChange = true;
}
i--;
}
}

// Close any remaining change
if (inChange) {
changes.unshift({
oldStart: 0,
oldEnd: oldEnd,
newStart: 0,
newEnd: newEnd
});
}

return changes;
}

private generateDiff(
oldLines: string[],
newLines: string[],
filePath: string
): string {
const CONTEXT_LINES = 3;

// Use LCS-based diff algorithm to find actual changes
const lcs = this.computeLCS(oldLines, newLines);
const changes = this.extractChanges(oldLines, newLines, lcs);

const hunks: Array<{
oldStart: number;
Expand Down
22 changes: 15 additions & 7 deletions src/utils/custom-instructions.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';

export function loadCustomInstructions(workingDirectory: string = process.cwd()): string | null {
try {
const instructionsPath = path.join(workingDirectory, '.grok', 'GROK.md');
let instructionsPath = path.join(workingDirectory, '.grok', 'GROK.md');

if (!fs.existsSync(instructionsPath)) {
return null;
if (fs.existsSync(instructionsPath)) {
const customInstructions = fs.readFileSync(instructionsPath, 'utf-8');
return customInstructions.trim();
}

const customInstructions = fs.readFileSync(instructionsPath, 'utf-8');
return customInstructions.trim();

instructionsPath = path.join(os.homedir(), '.grok', 'GROK.md');

if (fs.existsSync(instructionsPath)) {
const customInstructions = fs.readFileSync(instructionsPath, 'utf-8');
return customInstructions.trim();
}

return null;
} catch (error) {
console.warn('Failed to load custom instructions:', error);
return null;
}
}
}
Loading