nitpiq 0.1.0 → 0.3.0
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/bin/nitpiq +52 -0
- package/bin/nitpiq-mcp +52 -0
- package/package.json +9 -34
- package/postinstall.mjs +58 -0
- package/README.md +0 -173
- package/bin/nitpiq-mcp.ts +0 -2
- package/bin/nitpiq.ts +0 -2
- package/bunfig.toml +0 -1
- package/plugins/react-compiler.ts +0 -28
- package/src/cli/nitpiq-mcp.ts +0 -10
- package/src/cli/nitpiq.tsx +0 -36
- package/src/git/repo.ts +0 -237
- package/src/log/log.ts +0 -27
- package/src/mcp/server.test.ts +0 -37
- package/src/mcp/server.ts +0 -210
- package/src/review/anchor.ts +0 -118
- package/src/review/types.ts +0 -64
- package/src/store/store.ts +0 -315
- package/src/tui/app.tsx +0 -1089
- package/src/tui/demo.ts +0 -241
- package/src/tui/diff.ts +0 -99
- package/src/tui/highlight.ts +0 -208
- package/src/tui/theme.ts +0 -107
package/bin/nitpiq
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const childProcess = require("child_process");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const os = require("os");
|
|
7
|
+
|
|
8
|
+
function run(target) {
|
|
9
|
+
const result = childProcess.spawnSync(target, process.argv.slice(2), {
|
|
10
|
+
stdio: "inherit",
|
|
11
|
+
});
|
|
12
|
+
if (result.error) {
|
|
13
|
+
console.error(result.error.message);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
process.exit(typeof result.status === "number" ? result.status : 0);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const scriptDir = path.dirname(fs.realpathSync(__filename));
|
|
20
|
+
|
|
21
|
+
const cached = path.join(scriptDir, ".nitpiq");
|
|
22
|
+
if (fs.existsSync(cached)) {
|
|
23
|
+
run(cached);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const platformMap = { darwin: "darwin", linux: "linux" };
|
|
27
|
+
const archMap = { x64: "x64", arm64: "arm64" };
|
|
28
|
+
|
|
29
|
+
const platform = platformMap[os.platform()] || os.platform();
|
|
30
|
+
const arch = archMap[os.arch()] || os.arch();
|
|
31
|
+
const pkg = `nitpiq-${platform}-${arch}`;
|
|
32
|
+
|
|
33
|
+
function findBinary(startDir) {
|
|
34
|
+
let current = startDir;
|
|
35
|
+
for (;;) {
|
|
36
|
+
const candidate = path.join(current, "node_modules", pkg, "bin", "nitpiq");
|
|
37
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
38
|
+
const parent = path.dirname(current);
|
|
39
|
+
if (parent === current) return null;
|
|
40
|
+
current = parent;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const resolved = findBinary(scriptDir);
|
|
45
|
+
if (!resolved) {
|
|
46
|
+
console.error(
|
|
47
|
+
`Could not find the nitpiq binary. Try installing the platform package manually: npm install ${pkg}`,
|
|
48
|
+
);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
run(resolved);
|
package/bin/nitpiq-mcp
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const childProcess = require("child_process");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const os = require("os");
|
|
7
|
+
|
|
8
|
+
function run(target) {
|
|
9
|
+
const result = childProcess.spawnSync(target, process.argv.slice(2), {
|
|
10
|
+
stdio: "inherit",
|
|
11
|
+
});
|
|
12
|
+
if (result.error) {
|
|
13
|
+
console.error(result.error.message);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
process.exit(typeof result.status === "number" ? result.status : 0);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const scriptDir = path.dirname(fs.realpathSync(__filename));
|
|
20
|
+
|
|
21
|
+
const cached = path.join(scriptDir, ".nitpiq-mcp");
|
|
22
|
+
if (fs.existsSync(cached)) {
|
|
23
|
+
run(cached);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const platformMap = { darwin: "darwin", linux: "linux" };
|
|
27
|
+
const archMap = { x64: "x64", arm64: "arm64" };
|
|
28
|
+
|
|
29
|
+
const platform = platformMap[os.platform()] || os.platform();
|
|
30
|
+
const arch = archMap[os.arch()] || os.arch();
|
|
31
|
+
const pkg = `nitpiq-${platform}-${arch}`;
|
|
32
|
+
|
|
33
|
+
function findBinary(startDir) {
|
|
34
|
+
let current = startDir;
|
|
35
|
+
for (;;) {
|
|
36
|
+
const candidate = path.join(current, "node_modules", pkg, "bin", "nitpiq-mcp");
|
|
37
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
38
|
+
const parent = path.dirname(current);
|
|
39
|
+
if (parent === current) return null;
|
|
40
|
+
current = parent;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const resolved = findBinary(scriptDir);
|
|
45
|
+
if (!resolved) {
|
|
46
|
+
console.error(
|
|
47
|
+
`Could not find the nitpiq-mcp binary. Try installing the platform package manually: npm install ${pkg}`,
|
|
48
|
+
);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
run(resolved);
|
package/package.json
CHANGED
|
@@ -1,44 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nitpiq",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Terminal-based code review tool for local git changes",
|
|
5
|
-
"type": "module",
|
|
6
5
|
"license": "MIT",
|
|
7
6
|
"bin": {
|
|
8
|
-
"nitpiq": "bin/nitpiq
|
|
9
|
-
"nitpiq-mcp": "bin/nitpiq-mcp
|
|
7
|
+
"nitpiq": "bin/nitpiq",
|
|
8
|
+
"nitpiq-mcp": "bin/nitpiq-mcp"
|
|
10
9
|
},
|
|
11
|
-
"files": [
|
|
12
|
-
"bin/",
|
|
13
|
-
"src/",
|
|
14
|
-
"plugins/",
|
|
15
|
-
"bunfig.toml"
|
|
16
|
-
],
|
|
17
10
|
"scripts": {
|
|
18
|
-
"
|
|
19
|
-
"nitpiq-mcp": "bun run src/cli/nitpiq-mcp.ts",
|
|
20
|
-
"dev": "bun run src/cli/nitpiq.tsx",
|
|
21
|
-
"check": "tsc --noEmit"
|
|
11
|
+
"postinstall": "node postinstall.mjs"
|
|
22
12
|
},
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"@types/react": "^19.2.14",
|
|
29
|
-
"babel-plugin-react-compiler": "^1.0.0",
|
|
30
|
-
"eslint-plugin-react-hooks": "^7.0.1"
|
|
31
|
-
},
|
|
32
|
-
"peerDependencies": {
|
|
33
|
-
"typescript": "^5"
|
|
34
|
-
},
|
|
35
|
-
"dependencies": {
|
|
36
|
-
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
37
|
-
"fuse.js": "^7.1.0",
|
|
38
|
-
"ink": "^6.8.0",
|
|
39
|
-
"parse-diff": "^0.11.1",
|
|
40
|
-
"picocolors": "^1.1.1",
|
|
41
|
-
"react": "^19.2.4",
|
|
42
|
-
"zod": "^4.3.6"
|
|
13
|
+
"optionalDependencies": {
|
|
14
|
+
"nitpiq-linux-x64": "0.3.0",
|
|
15
|
+
"nitpiq-linux-arm64": "0.3.0",
|
|
16
|
+
"nitpiq-darwin-x64": "0.3.0",
|
|
17
|
+
"nitpiq-darwin-arm64": "0.3.0"
|
|
43
18
|
}
|
|
44
19
|
}
|
package/postinstall.mjs
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import os from "os";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import { createRequire } from "module";
|
|
8
|
+
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
|
|
12
|
+
const PLATFORM_MAP = { darwin: "darwin", linux: "linux" };
|
|
13
|
+
const ARCH_MAP = { x64: "x64", arm64: "arm64" };
|
|
14
|
+
|
|
15
|
+
function detect() {
|
|
16
|
+
const platform = PLATFORM_MAP[os.platform()];
|
|
17
|
+
const arch = ARCH_MAP[os.arch()];
|
|
18
|
+
if (!platform || !arch) {
|
|
19
|
+
throw new Error(`Unsupported platform: ${os.platform()}-${os.arch()}`);
|
|
20
|
+
}
|
|
21
|
+
return { platform, arch };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function findBinary(name) {
|
|
25
|
+
const { platform, arch } = detect();
|
|
26
|
+
const pkg = `nitpiq-${platform}-${arch}`;
|
|
27
|
+
|
|
28
|
+
const pkgJson = require.resolve(`${pkg}/package.json`);
|
|
29
|
+
const pkgDir = path.dirname(pkgJson);
|
|
30
|
+
const bin = path.join(pkgDir, "bin", name);
|
|
31
|
+
|
|
32
|
+
if (!fs.existsSync(bin)) {
|
|
33
|
+
throw new Error(`Binary not found at ${bin}`);
|
|
34
|
+
}
|
|
35
|
+
return bin;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function linkBinary(name) {
|
|
39
|
+
const source = findBinary(name);
|
|
40
|
+
const target = path.join(__dirname, "bin", `.${name}`);
|
|
41
|
+
|
|
42
|
+
if (fs.existsSync(target)) fs.unlinkSync(target);
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
fs.linkSync(source, target);
|
|
46
|
+
} catch {
|
|
47
|
+
fs.copyFileSync(source, target);
|
|
48
|
+
}
|
|
49
|
+
fs.chmodSync(target, 0o755);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
linkBinary("nitpiq");
|
|
54
|
+
linkBinary("nitpiq-mcp");
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.error("nitpiq postinstall:", error.message);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
package/README.md
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
# nitpiq
|
|
2
|
-
|
|
3
|
-
Terminal-based code review tool for local git changes. Built with Bun, TypeScript, React (Ink), and the React Compiler.
|
|
4
|
-
|
|
5
|
-
Inspect uncommitted changes, leave anchored review comments, and expose an MCP server so AI tools can participate as a second reviewer.
|
|
6
|
-
|
|
7
|
-
Review data is stored locally in `.git/nitpiq/review.db`.
|
|
8
|
-
|
|
9
|
-
## Prerequisites
|
|
10
|
-
|
|
11
|
-
- [Bun](https://bun.sh) v1.1+
|
|
12
|
-
|
|
13
|
-
## Install
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
bun install -g nitpiq
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Or from source:
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
git clone <repo-url> && cd nitpiq
|
|
23
|
-
bun install
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## Usage
|
|
27
|
-
|
|
28
|
-
### TUI
|
|
29
|
-
|
|
30
|
-
Run inside any git repository:
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
nitpiq
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Or with npx:
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
npx nitpiq
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Options:
|
|
43
|
-
|
|
44
|
-
```
|
|
45
|
-
--theme=<name> Set color theme (dark, catppuccin, nord, gruvbox)
|
|
46
|
-
--demo Launch with fixed demo data (no git required)
|
|
47
|
-
--snapshot Render a single frame and exit (for screenshots)
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
### MCP Server
|
|
51
|
-
|
|
52
|
-
Start the MCP server for AI tool integration:
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
nitpiq-mcp /path/to/repo
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
Or with npx (useful for MCP client configuration):
|
|
59
|
-
|
|
60
|
-
```bash
|
|
61
|
-
npx nitpiq-mcp /path/to/repo
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
The server exposes these tools over stdio:
|
|
65
|
-
|
|
66
|
-
| Tool | Description |
|
|
67
|
-
|------|-------------|
|
|
68
|
-
| `review_list_changes` | List uncommitted file changes |
|
|
69
|
-
| `review_list_threads` | List review threads (filterable by file/status) |
|
|
70
|
-
| `review_reply_thread` | Add a reply to a thread |
|
|
71
|
-
| `review_resolve_thread` | Mark a thread as resolved |
|
|
72
|
-
| `review_reopen_thread` | Reopen a resolved thread |
|
|
73
|
-
| `review_apply_edit` | Write content to a file |
|
|
74
|
-
| `review_stage_file` | Stage a file |
|
|
75
|
-
| `review_unstage_file` | Unstage a file |
|
|
76
|
-
|
|
77
|
-
#### MCP Client Configuration
|
|
78
|
-
|
|
79
|
-
For Claude Desktop, add to your config:
|
|
80
|
-
|
|
81
|
-
```json
|
|
82
|
-
{
|
|
83
|
-
"mcpServers": {
|
|
84
|
-
"nitpiq": {
|
|
85
|
-
"command": "npx",
|
|
86
|
-
"args": ["nitpiq-mcp", "/path/to/your/repo"]
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
For Cursor, add to `.cursor/mcp.json`:
|
|
93
|
-
|
|
94
|
-
```json
|
|
95
|
-
{
|
|
96
|
-
"mcpServers": {
|
|
97
|
-
"nitpiq": {
|
|
98
|
-
"command": "npx",
|
|
99
|
-
"args": ["nitpiq-mcp", "/path/to/your/repo"]
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
## Keybindings
|
|
106
|
-
|
|
107
|
-
### File Sidebar
|
|
108
|
-
|
|
109
|
-
| Key | Action |
|
|
110
|
-
|-----|--------|
|
|
111
|
-
| `j` / `k` | Navigate files |
|
|
112
|
-
| `l` / `Enter` | Open file in diff pane |
|
|
113
|
-
| `/` | Filter files by name |
|
|
114
|
-
| `s` | Stage / unstage file |
|
|
115
|
-
| `f` | Toggle between git changes and all files |
|
|
116
|
-
| `r` | Refresh |
|
|
117
|
-
| `Tab` | Switch to diff pane |
|
|
118
|
-
| `q` | Quit |
|
|
119
|
-
|
|
120
|
-
### Diff Pane
|
|
121
|
-
|
|
122
|
-
**Navigation (vim-style, supports count prefix e.g. `5j`):**
|
|
123
|
-
|
|
124
|
-
| Key | Action |
|
|
125
|
-
|-----|--------|
|
|
126
|
-
| `j` / `k` | Line up / down |
|
|
127
|
-
| `gg` | Jump to top (or `[count]gg` to line N) |
|
|
128
|
-
| `G` | Jump to bottom (or `[count]G` to line N) |
|
|
129
|
-
| `Ctrl+D` / `Ctrl+U` | Half-page down / up |
|
|
130
|
-
| `Ctrl+F` / `Ctrl+B` | Full-page down / up |
|
|
131
|
-
| `H` / `M` / `L` | Top / middle / bottom of visible screen |
|
|
132
|
-
| `{` / `}` | Previous / next block (hunk headers, blank lines) |
|
|
133
|
-
| `w` / `b` | Next / previous changed line |
|
|
134
|
-
| `[` / `]` | Previous / next review thread (cross-file) |
|
|
135
|
-
| `zz` / `zt` / `zb` | Center / top / bottom current line on screen |
|
|
136
|
-
| `:` | Go to line number |
|
|
137
|
-
|
|
138
|
-
**Actions:**
|
|
139
|
-
|
|
140
|
-
| Key | Action |
|
|
141
|
-
|-----|--------|
|
|
142
|
-
| `c` | Comment on current line (or reply if thread exists) |
|
|
143
|
-
| `v` | Enter visual mode for range selection |
|
|
144
|
-
| `d` | Delete thread at cursor (with confirmation) |
|
|
145
|
-
| `r` | Resolve / reopen thread at cursor |
|
|
146
|
-
| `/` | Search diff |
|
|
147
|
-
| `n` / `N` | Next / previous search match |
|
|
148
|
-
| `f` | Toggle diff view / full file view |
|
|
149
|
-
| `e` | Cycle diff context (3 / 10 / full) |
|
|
150
|
-
| `h` / `Esc` / `q` | Back to file sidebar |
|
|
151
|
-
|
|
152
|
-
### Visual Mode
|
|
153
|
-
|
|
154
|
-
| Key | Action |
|
|
155
|
-
|-----|--------|
|
|
156
|
-
| `j` / `k` | Extend selection |
|
|
157
|
-
| `c` | Comment on selected range |
|
|
158
|
-
| `Esc` | Cancel |
|
|
159
|
-
|
|
160
|
-
## Themes
|
|
161
|
-
|
|
162
|
-
Set with `--theme=<name>`:
|
|
163
|
-
|
|
164
|
-
- **dark** (default) -- blue accent on dark background
|
|
165
|
-
- **catppuccin** -- pastel mocha palette
|
|
166
|
-
- **nord** -- arctic blue tones
|
|
167
|
-
- **gruvbox** -- warm retro colors
|
|
168
|
-
|
|
169
|
-
## Type Check
|
|
170
|
-
|
|
171
|
-
```bash
|
|
172
|
-
bun run check
|
|
173
|
-
```
|
package/bin/nitpiq-mcp.ts
DELETED
package/bin/nitpiq.ts
DELETED
package/bunfig.toml
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
preload = ["./plugins/react-compiler.ts"]
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { plugin } from "bun";
|
|
2
|
-
import { transformSync } from "@babel/core";
|
|
3
|
-
import ReactCompilerPlugin from "babel-plugin-react-compiler";
|
|
4
|
-
|
|
5
|
-
plugin({
|
|
6
|
-
name: "react-compiler",
|
|
7
|
-
setup(build) {
|
|
8
|
-
build.onLoad({ filter: /\.tsx$/ }, async (args) => {
|
|
9
|
-
const source = await Bun.file(args.path).text();
|
|
10
|
-
|
|
11
|
-
const result = transformSync(source, {
|
|
12
|
-
filename: args.path,
|
|
13
|
-
plugins: [
|
|
14
|
-
[ReactCompilerPlugin, {}],
|
|
15
|
-
["@babel/plugin-syntax-typescript", { isTSX: true }],
|
|
16
|
-
"@babel/plugin-syntax-jsx",
|
|
17
|
-
],
|
|
18
|
-
configFile: false,
|
|
19
|
-
babelrc: false,
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
return {
|
|
23
|
-
contents: result?.code ?? source,
|
|
24
|
-
loader: "tsx",
|
|
25
|
-
};
|
|
26
|
-
});
|
|
27
|
-
},
|
|
28
|
-
});
|
package/src/cli/nitpiq-mcp.ts
DELETED
package/src/cli/nitpiq.tsx
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { render } from "ink";
|
|
3
|
-
import { openRepoAt } from "../git/repo";
|
|
4
|
-
import { initLog } from "../log/log";
|
|
5
|
-
import { Store } from "../store/store";
|
|
6
|
-
import { NitpiqApp } from "../tui/app";
|
|
7
|
-
import { createDemoState } from "../tui/demo";
|
|
8
|
-
|
|
9
|
-
try {
|
|
10
|
-
const args = process.argv.slice(2);
|
|
11
|
-
const demo = args.includes("--demo");
|
|
12
|
-
const snapshot = args.includes("--snapshot");
|
|
13
|
-
const themeArg = args.find((a) => a.startsWith("--theme="));
|
|
14
|
-
const themeName = themeArg?.split("=")[1];
|
|
15
|
-
const demoState = demo ? createDemoState() : undefined;
|
|
16
|
-
const repo = demoState?.repo ?? openRepoAt(process.cwd());
|
|
17
|
-
const store = demo ? null : Store.open(repo.root);
|
|
18
|
-
if (!demo) {
|
|
19
|
-
initLog(repo.root);
|
|
20
|
-
}
|
|
21
|
-
const useAltScreen = Boolean(process.stdout.isTTY);
|
|
22
|
-
if (useAltScreen) {
|
|
23
|
-
process.stdout.write("\u001b[?1049h\u001b[H");
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const instance = render(<NitpiqApp repo={repo} store={store} demoState={demoState} snapshot={snapshot} theme={themeName} />);
|
|
27
|
-
instance.waitUntilExit().finally(() => {
|
|
28
|
-
if (useAltScreen) {
|
|
29
|
-
process.stdout.write("\u001b[?1049l");
|
|
30
|
-
}
|
|
31
|
-
store?.close();
|
|
32
|
-
});
|
|
33
|
-
} catch (error) {
|
|
34
|
-
console.error(`nitpiq: ${error instanceof Error ? error.message : String(error)}`);
|
|
35
|
-
process.exit(1);
|
|
36
|
-
}
|