reviuah 0.1.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/LICENSE +21 -0
- package/README.md +208 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +62 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/config-status.d.ts +1 -0
- package/dist/commands/config-status.js +20 -0
- package/dist/commands/config-status.js.map +1 -0
- package/dist/commands/review.d.ts +12 -0
- package/dist/commands/review.js +94 -0
- package/dist/commands/review.js.map +1 -0
- package/dist/commands/setup.d.ts +1 -0
- package/dist/commands/setup.js +70 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/config/user-config.d.ts +16 -0
- package/dist/config/user-config.js +52 -0
- package/dist/config/user-config.js.map +1 -0
- package/dist/git/diff.d.ts +3 -0
- package/dist/git/diff.js +19 -0
- package/dist/git/diff.js.map +1 -0
- package/dist/lib/read-secret.d.ts +4 -0
- package/dist/lib/read-secret.js +69 -0
- package/dist/lib/read-secret.js.map +1 -0
- package/dist/providers/index.d.ts +12 -0
- package/dist/providers/index.js +2 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/openai.d.ts +20 -0
- package/dist/providers/openai.js +124 -0
- package/dist/providers/openai.js.map +1 -0
- package/package.json +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) ReviuAh contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# ReviuAh
|
|
2
|
+
|
|
3
|
+
> AI-powered CLI to review Git diffs before commit or push.\
|
|
4
|
+
> Install via **npm**, **yarn**, or **pnpm**; use on your branch with `--base` or `reviewah`.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
### Kalau `npm install -g reviuah` error 404
|
|
11
|
+
|
|
12
|
+
Paket ini **belum dipublish** ke [npm](https://www.npmjs.com/package/reviuah) (atau namanya belum kamu ambil). Pakai salah satu di bawah.
|
|
13
|
+
|
|
14
|
+
**Dari GitHub (global, build otomatis saat install):**
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install -g git+https://github.com/rsuregar/reviewah.git
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Lokal (clone):**
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
git clone https://github.com/rsuregar/reviewah.git && cd reviewah
|
|
24
|
+
npm install
|
|
25
|
+
npm run build
|
|
26
|
+
npm link
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Setelah publish ke npm
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install -g reviuah
|
|
33
|
+
# atau: yarn global add reviuah / pnpm add -g reviuah
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Publish: login `npm login`, lalu di repo `npm publish --access public` (butuh akun npm & nama `reviuah` belum dipakai orang lain).
|
|
37
|
+
|
|
38
|
+
**Perintah:** `reviuah` atau **`reviewah`** (alias sama).
|
|
39
|
+
|
|
40
|
+
**Setup API key (disarankan):**
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
reviuah setup
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Menyimpan key ke **`~/.reviuah/config.json`** (hanya di mesin kamu). Setelah itu cukup jalankan `reviuah` tanpa export env.
|
|
47
|
+
|
|
48
|
+
**Atau** set env (mengoverride file): `REVIUAH_API_KEY`, opsional `REVIUAH_PROVIDER`, `REVIUAH_MODEL`, `REVIUAH_PROVIDER_URL`.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
reviuah config # cek lokasi file & apakah key sudah tersimpan
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Inspirasi: [Commitah](https://github.com/utsmannn/commitah) vs ReviuAh
|
|
57
|
+
|
|
58
|
+
[Commitah](https://github.com/utsmannn/commitah) pakai satu perintah CLI (`commitah`): analisis **perubahan yang sudah di-stage** → AI menghasilkan **pesan commit** (conventional commits, UI interaktif).
|
|
59
|
+
|
|
60
|
+
**ReviuAh** memakai pola yang sama untuk input default:
|
|
61
|
+
|
|
62
|
+
| | Commitah | ReviuAh |
|
|
63
|
+
|---|----------|---------|
|
|
64
|
+
| Perintah | `commitah` | `reviuah` (tanpa opsi) |
|
|
65
|
+
| Sumber diff | Staged (`git add` dulu) | Sama |
|
|
66
|
+
| Output | Saran commit message | **Review** (summary, risiko, security, testing, …) |
|
|
67
|
+
|
|
68
|
+
Jadi: **Commitah** = “tulis commit message dari diff”; **ReviuAh** = “tinjau diff sebelum commit/push”. Keduanya bisa dipakai berurutan, misalnya `reviuah` dulu (cek kualitas), lalu `commitah` (pesan commit). Detail singkat: [docs/inspirasi-commitah.md](./docs/inspirasi-commitah.md).
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Cara pakai (alur branch)
|
|
73
|
+
|
|
74
|
+
Typical flow: kamu sedang di **branch fitur**, ingin review selisih terhadap `main`:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
git checkout feature/kerjaan-kamu
|
|
78
|
+
export REVIUAH_API_KEY="sk-..." # atau key provider lain
|
|
79
|
+
|
|
80
|
+
# Review branch ini vs main (setara git diff main...HEAD)
|
|
81
|
+
reviuah --base main
|
|
82
|
+
|
|
83
|
+
# Kalau remote default beda nama branch
|
|
84
|
+
reviuah --base origin/main
|
|
85
|
+
|
|
86
|
+
# Simpan ke file (buka di editor / share ke PR)
|
|
87
|
+
reviuah --base main --out review.md --lang id
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
| Skenario | Perintah |
|
|
91
|
+
|----------|----------|
|
|
92
|
+
| Hanya perubahan yang sudah **di-stage** | `reviuah` |
|
|
93
|
+
| Satu **commit** | `reviuah --commit HEAD` |
|
|
94
|
+
| Range manual | `reviuah --range main...HEAD` |
|
|
95
|
+
| **Branch aktif** vs base | `reviuah --base main` |
|
|
96
|
+
| CI: gagal jika risk **high** | `reviuah --base origin/main --strict` |
|
|
97
|
+
|
|
98
|
+
**Tip:** di shell bisa alias supaya “satu klik” dari terminal:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
alias reviewah='reviuah --base main --lang id'
|
|
102
|
+
# lalu: reviewah
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## CLI (English)
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
reviuah --help
|
|
111
|
+
reviuah --version
|
|
112
|
+
|
|
113
|
+
reviuah # staged diff
|
|
114
|
+
reviuah --commit HEAD
|
|
115
|
+
reviuah --range origin/main...HEAD
|
|
116
|
+
reviuah --base main # branch vs main
|
|
117
|
+
reviuah --out review.md
|
|
118
|
+
reviuah --strict
|
|
119
|
+
reviuah --lang en
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Required review output (Markdown)
|
|
125
|
+
|
|
126
|
+
1. Summary
|
|
127
|
+
2. Risk Level (low / medium / high / unknown) + reason
|
|
128
|
+
3. Security Review
|
|
129
|
+
4. Performance Review
|
|
130
|
+
5. Testing Suggestions
|
|
131
|
+
6. Code Quality & Maintainability
|
|
132
|
+
7. Actionable Suggestions
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Architecture (current)
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
src/
|
|
140
|
+
cli.ts
|
|
141
|
+
commands/review.ts
|
|
142
|
+
git/diff.ts
|
|
143
|
+
providers/
|
|
144
|
+
index.ts
|
|
145
|
+
openai.ts
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Environment variables
|
|
151
|
+
|
|
152
|
+
| Variable | Description |
|
|
153
|
+
|----------|-------------|
|
|
154
|
+
| `REVIUAH_API_KEY` | Required for API calls |
|
|
155
|
+
| `REVIUAH_PROVIDER` | Default preset: **`agentrouter`** → `https://agentrouter.org/v1`. Lainnya: `openai`, `gemini`, `deepseek`, `ollama`, … |
|
|
156
|
+
| `REVIUAH_PROVIDER_URL` | Override base URL |
|
|
157
|
+
| `REVIUAH_MODEL` | Override model (default untuk agentrouter: `gpt-4o`; sesuaikan di dashboard AgentRouter) |
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## CI (GitHub Actions)
|
|
162
|
+
|
|
163
|
+
```yaml
|
|
164
|
+
name: AI Review
|
|
165
|
+
on: [pull_request]
|
|
166
|
+
|
|
167
|
+
jobs:
|
|
168
|
+
review:
|
|
169
|
+
runs-on: ubuntu-latest
|
|
170
|
+
steps:
|
|
171
|
+
- uses: actions/checkout@v4
|
|
172
|
+
with:
|
|
173
|
+
fetch-depth: 0
|
|
174
|
+
- uses: actions/setup-node@v4
|
|
175
|
+
with:
|
|
176
|
+
node-version: "20"
|
|
177
|
+
- run: npm install -g reviuah
|
|
178
|
+
- run: reviuah --range origin/${{ github.base_ref }}...HEAD --strict
|
|
179
|
+
env:
|
|
180
|
+
REVIUAH_API_KEY: ${{ secrets.REVIUAH_API_KEY }}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Untuk `pull_request`, checkout default adalah merge commit; untuk diff seperti di lokal, fetch branch dan gunakan `--base origin/main` atau `--range origin/main...HEAD` setelah checkout ke **head** PR (lihat [docs/cara-pakai.md](./docs/cara-pakai.md)).
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## For AI assistants
|
|
188
|
+
|
|
189
|
+
- [AGENTS.md](./AGENTS.md) · [CLAUDE.md](./CLAUDE.md) · `.cursor/skills/`
|
|
190
|
+
|
|
191
|
+
## Development
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
yarn install
|
|
195
|
+
yarn build
|
|
196
|
+
yarn link # lokal: perintah reviuah / reviewah dari repo ini
|
|
197
|
+
reviuah --help
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Publish manual: `npm publish --access public` (setelah `yarn build` dan login npm).
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Design philosophy
|
|
205
|
+
|
|
206
|
+
ReviuAh is a **pre-review** assistant and CI gate (`--strict`), not a replacement for human review.
|
|
207
|
+
|
|
208
|
+
Lihat juga **[docs/cara-pakai.md](./docs/cara-pakai.md)** (ringkas, Indonesia).
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { reviewCommand } from "./commands/review.js";
|
|
7
|
+
import { setupCommand } from "./commands/setup.js";
|
|
8
|
+
import { configStatusCommand } from "./commands/config-status.js";
|
|
9
|
+
function readPkgVersion() {
|
|
10
|
+
try {
|
|
11
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const pkgPath = join(here, "..", "package.json");
|
|
13
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
14
|
+
return pkg.version ?? "0.0.0";
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return "0.0.0";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const program = new Command();
|
|
21
|
+
program
|
|
22
|
+
.name("reviuah")
|
|
23
|
+
.description("AI code review from Git diff (default: staged changes — same diff source idea as commitah, output is review not commit msg)")
|
|
24
|
+
.version(readPkgVersion());
|
|
25
|
+
program
|
|
26
|
+
.command("setup")
|
|
27
|
+
.description("Interactive: simpan API key & provider ke ~/.reviuah/config.json (dipakai jika env tidak diset)")
|
|
28
|
+
.action(async () => {
|
|
29
|
+
await setupCommand();
|
|
30
|
+
});
|
|
31
|
+
program
|
|
32
|
+
.command("config")
|
|
33
|
+
.description("Tampilkan lokasi file config dan status API key")
|
|
34
|
+
.action(async () => {
|
|
35
|
+
await configStatusCommand();
|
|
36
|
+
});
|
|
37
|
+
program
|
|
38
|
+
.option("--commit <ref>", "review a specific commit")
|
|
39
|
+
.option("--range <range>", "review a git range, e.g. main...HEAD or origin/main...HEAD")
|
|
40
|
+
.option("--base <ref>", "review current branch vs base (same as --range <ref>...HEAD), e.g. main or origin/main")
|
|
41
|
+
.option("--strict", "exit with code 1 when risk level is high", false)
|
|
42
|
+
.option("--lang <lang>", "output language", "en")
|
|
43
|
+
.option("--out <file>", "write review markdown to file")
|
|
44
|
+
.action(async (options) => {
|
|
45
|
+
const result = await reviewCommand({
|
|
46
|
+
commit: options.commit,
|
|
47
|
+
range: options.range,
|
|
48
|
+
base: options.base,
|
|
49
|
+
strict: Boolean(options.strict),
|
|
50
|
+
lang: options.lang ?? "en",
|
|
51
|
+
out: options.out,
|
|
52
|
+
});
|
|
53
|
+
if (options.strict && result.risk === "high") {
|
|
54
|
+
process.exitCode = 1;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
program.parseAsync(process.argv).catch((error) => {
|
|
58
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
59
|
+
console.error(`Error: ${message}`);
|
|
60
|
+
process.exitCode = 1;
|
|
61
|
+
});
|
|
62
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function configStatusCommand(): Promise<void>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getUserConfigPath, maskApiKey, readUserConfig, } from "../config/user-config.js";
|
|
2
|
+
export async function configStatusCommand() {
|
|
3
|
+
const path = getUserConfigPath();
|
|
4
|
+
const cfg = await readUserConfig();
|
|
5
|
+
console.log(`Config file: ${path}`);
|
|
6
|
+
if (!cfg?.apiKey) {
|
|
7
|
+
console.log("API key: (belum diset — jalankan: reviuah setup)");
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
console.log(`API key: ${maskApiKey(cfg.apiKey)} (tersimpan)`);
|
|
11
|
+
}
|
|
12
|
+
if (cfg?.provider)
|
|
13
|
+
console.log(`Provider: ${cfg.provider}`);
|
|
14
|
+
if (cfg?.model)
|
|
15
|
+
console.log(`Model: ${cfg.model}`);
|
|
16
|
+
if (cfg?.providerUrl)
|
|
17
|
+
console.log(`Base URL: ${cfg.providerUrl}`);
|
|
18
|
+
console.log("\nEnv REVIUAH_API_KEY / REVIUAH_PROVIDER / … mengoverride file ini.");
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=config-status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-status.js","sourceRoot":"","sources":["../../src/commands/config-status.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,UAAU,EACV,cAAc,GACf,MAAM,0BAA0B,CAAC;AAElC,MAAM,CAAC,KAAK,UAAU,mBAAmB;IACvC,MAAM,IAAI,GAAG,iBAAiB,EAAE,CAAC;IACjC,MAAM,GAAG,GAAG,MAAM,cAAc,EAAE,CAAC;IAEnC,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,EAAE,CAAC,CAAC;IACpC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC;QACjB,OAAO,CAAC,GAAG,CAAC,kDAAkD,CAAC,CAAC;IAClE,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,YAAY,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IAChE,CAAC;IACD,IAAI,GAAG,EAAE,QAAQ;QAAE,OAAO,CAAC,GAAG,CAAC,aAAa,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC5D,IAAI,GAAG,EAAE,KAAK;QAAE,OAAO,CAAC,GAAG,CAAC,UAAU,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;IACnD,IAAI,GAAG,EAAE,WAAW;QAAE,OAAO,CAAC,GAAG,CAAC,aAAa,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;IAClE,OAAO,CAAC,GAAG,CAAC,qEAAqE,CAAC,CAAC;AACrF,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReviewResponse } from "../providers/index.js";
|
|
2
|
+
export interface ReviewCommandOptions {
|
|
3
|
+
commit?: string;
|
|
4
|
+
range?: string;
|
|
5
|
+
/** Base ref vs current HEAD (e.g. main → diff main...HEAD). */
|
|
6
|
+
base?: string;
|
|
7
|
+
strict: boolean;
|
|
8
|
+
lang: string;
|
|
9
|
+
/** Write review markdown to this path instead of only stdout. */
|
|
10
|
+
out?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function reviewCommand(options: ReviewCommandOptions): Promise<ReviewResponse>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { writeFile } from "node:fs/promises";
|
|
2
|
+
import { execa } from "execa";
|
|
3
|
+
import { getCommitDiff, getRangeDiff, getStagedDiff } from "../git/diff.js";
|
|
4
|
+
import { OpenAIProvider } from "../providers/openai.js";
|
|
5
|
+
import { resolveReviewCredentials } from "../config/user-config.js";
|
|
6
|
+
const MAX_DIFF_SIZE = 120000;
|
|
7
|
+
export async function reviewCommand(options) {
|
|
8
|
+
await ensureGitRepository();
|
|
9
|
+
const diff = await resolveDiff(options);
|
|
10
|
+
if (!diff) {
|
|
11
|
+
const emptyMarkdown = [
|
|
12
|
+
"## Summary",
|
|
13
|
+
"No changes detected in the selected diff scope.",
|
|
14
|
+
"",
|
|
15
|
+
"## Risk Level",
|
|
16
|
+
"Unknown",
|
|
17
|
+
"Reason: No diff content to review.",
|
|
18
|
+
"",
|
|
19
|
+
"## Security Review",
|
|
20
|
+
"No changes to analyze.",
|
|
21
|
+
"",
|
|
22
|
+
"## Performance Review",
|
|
23
|
+
"No changes to analyze.",
|
|
24
|
+
"",
|
|
25
|
+
"## Testing Suggestions",
|
|
26
|
+
"No testing required.",
|
|
27
|
+
"",
|
|
28
|
+
"## Code Quality & Maintainability",
|
|
29
|
+
"No changes to assess.",
|
|
30
|
+
"",
|
|
31
|
+
"## Actionable Suggestions",
|
|
32
|
+
"- Stage or select changes before running ReviuAh.",
|
|
33
|
+
].join("\n");
|
|
34
|
+
await outputReview(emptyMarkdown, options.out);
|
|
35
|
+
return { markdown: emptyMarkdown, risk: "unknown" };
|
|
36
|
+
}
|
|
37
|
+
const trimmedDiff = trimDiff(diff, MAX_DIFF_SIZE);
|
|
38
|
+
const { apiKey, baseURL, model } = await resolveReviewCredentials();
|
|
39
|
+
const provider = new OpenAIProvider({
|
|
40
|
+
apiKey,
|
|
41
|
+
baseURL,
|
|
42
|
+
model,
|
|
43
|
+
});
|
|
44
|
+
const result = await provider.review({
|
|
45
|
+
diff: trimmedDiff,
|
|
46
|
+
language: options.lang,
|
|
47
|
+
});
|
|
48
|
+
await outputReview(result.markdown, options.out);
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
async function outputReview(markdown, outPath) {
|
|
52
|
+
if (outPath) {
|
|
53
|
+
await writeFile(outPath, markdown, "utf8");
|
|
54
|
+
console.error(`ReviuAh: review written to ${outPath}`);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
console.log(markdown);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function ensureGitRepository() {
|
|
61
|
+
const check = await execa("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
62
|
+
reject: false,
|
|
63
|
+
});
|
|
64
|
+
if (check.exitCode !== 0 || check.stdout.trim() !== "true") {
|
|
65
|
+
throw new Error("ReviuAh must be run inside a git repository.");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function resolveDiff(options) {
|
|
69
|
+
const modes = [
|
|
70
|
+
Boolean(options.commit),
|
|
71
|
+
Boolean(options.range),
|
|
72
|
+
Boolean(options.base),
|
|
73
|
+
].filter(Boolean).length;
|
|
74
|
+
if (modes > 1) {
|
|
75
|
+
throw new Error("Use only one of: default (staged), --commit, --range, or --base.");
|
|
76
|
+
}
|
|
77
|
+
if (options.commit) {
|
|
78
|
+
return getCommitDiff(options.commit);
|
|
79
|
+
}
|
|
80
|
+
if (options.range) {
|
|
81
|
+
return getRangeDiff(options.range);
|
|
82
|
+
}
|
|
83
|
+
if (options.base) {
|
|
84
|
+
return getRangeDiff(`${options.base}...HEAD`);
|
|
85
|
+
}
|
|
86
|
+
return getStagedDiff();
|
|
87
|
+
}
|
|
88
|
+
function trimDiff(diff, maxSize) {
|
|
89
|
+
if (diff.length <= maxSize) {
|
|
90
|
+
return diff;
|
|
91
|
+
}
|
|
92
|
+
return `${diff.slice(0, maxSize)}\n\n[Diff truncated to ${maxSize} characters]`;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=review.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review.js","sourceRoot":"","sources":["../../src/commands/review.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function setupCommand(): Promise<void>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as readline from "node:readline/promises";
|
|
2
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
3
|
+
import { getUserConfigPath, maskApiKey, readUserConfig, writeUserConfig, } from "../config/user-config.js";
|
|
4
|
+
import { readSecretLine } from "../lib/read-secret.js";
|
|
5
|
+
import { listProviderIds, resolveProviderDefaults } from "../providers/openai.js";
|
|
6
|
+
async function question(defaultLabel, def) {
|
|
7
|
+
const rl = readline.createInterface({ input, output });
|
|
8
|
+
try {
|
|
9
|
+
const hint = def !== undefined ? `${defaultLabel} [${def}]: ` : `${defaultLabel}: `;
|
|
10
|
+
const line = (await rl.question(hint)).trim();
|
|
11
|
+
return line || def || "";
|
|
12
|
+
}
|
|
13
|
+
finally {
|
|
14
|
+
rl.close();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function setupCommand() {
|
|
18
|
+
const existing = await readUserConfig();
|
|
19
|
+
const path = getUserConfigPath();
|
|
20
|
+
console.error("ReviuAh setup — menyimpan konfigurasi di:");
|
|
21
|
+
console.error(` ${path}`);
|
|
22
|
+
console.error("(File hanya di komputer kamu; jangan di-commit. Env REVIUAH_* tetap mengoverride.)\n");
|
|
23
|
+
let apiKey;
|
|
24
|
+
if (existing?.apiKey) {
|
|
25
|
+
const keep = (await question(`Pertahankan API key saat ini (${maskApiKey(existing.apiKey)})? ketik y atau n`, "y")).toLowerCase();
|
|
26
|
+
if (keep === "n" || keep === "no") {
|
|
27
|
+
apiKey = await readSecretLine("API key baru: ");
|
|
28
|
+
while (!apiKey.trim()) {
|
|
29
|
+
console.error("API key wajib diisi.");
|
|
30
|
+
apiKey = await readSecretLine("API key baru: ");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
apiKey = existing.apiKey;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
apiKey = await readSecretLine("API key: ");
|
|
39
|
+
while (!apiKey.trim()) {
|
|
40
|
+
console.error("API key wajib diisi.");
|
|
41
|
+
apiKey = await readSecretLine("API key: ");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const ids = listProviderIds().join(", ");
|
|
45
|
+
const defaultProv = existing?.provider?.trim() || "agentrouter";
|
|
46
|
+
const provInput = await question(`Provider (${ids})`, defaultProv);
|
|
47
|
+
const provider = provInput.toLowerCase() || defaultProv;
|
|
48
|
+
const defaults = resolveProviderDefaults(provider);
|
|
49
|
+
const rl2 = readline.createInterface({ input, output });
|
|
50
|
+
let modelLine;
|
|
51
|
+
let urlLine;
|
|
52
|
+
try {
|
|
53
|
+
modelLine = (await rl2.question(`Model (Enter = ${defaults.defaultModel}): `)).trim();
|
|
54
|
+
urlLine = (await rl2.question(`Base URL (Enter = ${defaults.baseURL}): `)).trim();
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
rl2.close();
|
|
58
|
+
}
|
|
59
|
+
const model = modelLine || defaults.defaultModel;
|
|
60
|
+
const providerUrl = urlLine || defaults.baseURL;
|
|
61
|
+
const next = {
|
|
62
|
+
apiKey: apiKey.trim(),
|
|
63
|
+
provider,
|
|
64
|
+
model,
|
|
65
|
+
providerUrl,
|
|
66
|
+
};
|
|
67
|
+
await writeUserConfig(next);
|
|
68
|
+
console.error("\nSelesai. Coba: reviuah (review staged) atau reviuah --base main");
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface UserConfig {
|
|
2
|
+
apiKey?: string;
|
|
3
|
+
provider?: string;
|
|
4
|
+
providerUrl?: string;
|
|
5
|
+
model?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function getUserConfigPath(): string;
|
|
8
|
+
export declare function readUserConfig(): Promise<UserConfig | null>;
|
|
9
|
+
export declare function writeUserConfig(config: UserConfig): Promise<void>;
|
|
10
|
+
export declare function maskApiKey(key: string): string;
|
|
11
|
+
/** Env wins over file (CI-friendly). */
|
|
12
|
+
export declare function resolveReviewCredentials(): Promise<{
|
|
13
|
+
apiKey: string;
|
|
14
|
+
baseURL: string;
|
|
15
|
+
model: string;
|
|
16
|
+
}>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile, chmod } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { resolveProviderDefaults } from "../providers/openai.js";
|
|
5
|
+
const CONFIG_DIR = join(homedir(), ".reviuah");
|
|
6
|
+
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
7
|
+
export function getUserConfigPath() {
|
|
8
|
+
return CONFIG_FILE;
|
|
9
|
+
}
|
|
10
|
+
export async function readUserConfig() {
|
|
11
|
+
try {
|
|
12
|
+
const raw = await readFile(CONFIG_FILE, "utf8");
|
|
13
|
+
return JSON.parse(raw);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export async function writeUserConfig(config) {
|
|
20
|
+
await mkdir(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
21
|
+
const body = `${JSON.stringify(config, null, 2)}\n`;
|
|
22
|
+
await writeFile(CONFIG_FILE, body, { mode: 0o600 });
|
|
23
|
+
try {
|
|
24
|
+
await chmod(CONFIG_FILE, 0o600);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
/* Windows may ignore */
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function maskApiKey(key) {
|
|
31
|
+
if (key.length <= 8)
|
|
32
|
+
return "****";
|
|
33
|
+
return `${key.slice(0, 4)}…${key.slice(-4)}`;
|
|
34
|
+
}
|
|
35
|
+
/** Env wins over file (CI-friendly). */
|
|
36
|
+
export async function resolveReviewCredentials() {
|
|
37
|
+
const file = await readUserConfig();
|
|
38
|
+
const apiKey = process.env.REVIUAH_API_KEY?.trim() || file?.apiKey?.trim();
|
|
39
|
+
if (!apiKey) {
|
|
40
|
+
throw new Error("No API key. Run: reviuah setup\nOr set REVIUAH_API_KEY in your environment.");
|
|
41
|
+
}
|
|
42
|
+
const providerName = process.env.REVIUAH_PROVIDER?.trim() || file?.provider?.trim();
|
|
43
|
+
const defaults = resolveProviderDefaults(providerName);
|
|
44
|
+
const baseURL = process.env.REVIUAH_PROVIDER_URL?.trim() ||
|
|
45
|
+
file?.providerUrl?.trim() ||
|
|
46
|
+
defaults.baseURL;
|
|
47
|
+
const model = process.env.REVIUAH_MODEL?.trim() ||
|
|
48
|
+
file?.model?.trim() ||
|
|
49
|
+
defaults.defaultModel;
|
|
50
|
+
return { apiKey, baseURL, model };
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=user-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-config.js","sourceRoot":"","sources":["../../src/config/user-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AASjE,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,UAAU,CAAC,CAAC;AAC/C,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AAEpD,MAAM,UAAU,iBAAiB;IAC/B,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QAChD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAe,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,MAAkB;IACtD,MAAM,KAAK,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1D,MAAM,IAAI,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;IACpD,MAAM,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACpD,IAAI,CAAC;QACH,MAAM,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,wBAAwB;IAC1B,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,MAAM,CAAC;IACnC,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED,wCAAwC;AACxC,MAAM,CAAC,KAAK,UAAU,wBAAwB;IAK5C,MAAM,IAAI,GAAG,MAAM,cAAc,EAAE,CAAC;IAEpC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,EAAE,IAAI,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAC3E,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,6EAA6E,CAC9E,CAAC;IACJ,CAAC;IAED,MAAM,YAAY,GAChB,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAI,EAAE,IAAI,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACjE,MAAM,QAAQ,GAAG,uBAAuB,CAAC,YAAY,CAAC,CAAC;IAEvD,MAAM,OAAO,GACX,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,IAAI,EAAE;QACxC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE;QACzB,QAAQ,CAAC,OAAO,CAAC;IACnB,MAAM,KAAK,GACT,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,EAAE;QACjC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE;QACnB,QAAQ,CAAC,YAAY,CAAC;IAExB,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AACpC,CAAC"}
|
package/dist/git/diff.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { execa } from "execa";
|
|
2
|
+
async function runGitDiff(args) {
|
|
3
|
+
const result = await execa("git", args, { reject: false });
|
|
4
|
+
if (result.exitCode !== 0) {
|
|
5
|
+
const message = result.stderr.trim() || "Unknown git error";
|
|
6
|
+
throw new Error(`Failed to run git ${args.join(" ")}: ${message}`);
|
|
7
|
+
}
|
|
8
|
+
return result.stdout.trim();
|
|
9
|
+
}
|
|
10
|
+
export async function getStagedDiff() {
|
|
11
|
+
return runGitDiff(["diff", "--cached"]);
|
|
12
|
+
}
|
|
13
|
+
export async function getCommitDiff(ref) {
|
|
14
|
+
return runGitDiff(["show", "--format=", ref]);
|
|
15
|
+
}
|
|
16
|
+
export async function getRangeDiff(range) {
|
|
17
|
+
return runGitDiff(["diff", range]);
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/git/diff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAE9B,KAAK,UAAU,UAAU,CAAC,IAAc;IACtC,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IAE3D,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,mBAAmB,CAAC;QAC5D,MAAM,IAAI,KAAK,CAAC,qBAAqB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,OAAO,EAAE,CAAC,CAAC;IACrE,CAAC;IAED,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;AAC9B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,OAAO,UAAU,CAAC,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAW;IAC7C,OAAO,UAAU,CAAC,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,KAAa;IAC9C,OAAO,UAAU,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;AACrC,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import * as readline from "node:readline/promises";
|
|
2
|
+
import { createInterface } from "node:readline";
|
|
3
|
+
/**
|
|
4
|
+
* Reads a line without echoing characters (TTY). Falls back to visible input if raw mode fails.
|
|
5
|
+
*/
|
|
6
|
+
export async function readSecretLine(prompt) {
|
|
7
|
+
const stdin = process.stdin;
|
|
8
|
+
const stdout = process.stdout;
|
|
9
|
+
if (!stdin.isTTY) {
|
|
10
|
+
const rl = readline.createInterface({ input: stdin, output: stdout });
|
|
11
|
+
try {
|
|
12
|
+
stdout.write(`${prompt}(piped input — key will be visible)\n`);
|
|
13
|
+
return (await rl.question("API key: ")).trim();
|
|
14
|
+
}
|
|
15
|
+
finally {
|
|
16
|
+
rl.close();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
stdout.write(prompt);
|
|
21
|
+
try {
|
|
22
|
+
stdin.setRawMode(true);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
stdin.setRawMode(false);
|
|
26
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
27
|
+
rl.question("API key (visible): ", (answer) => {
|
|
28
|
+
rl.close();
|
|
29
|
+
resolve(answer.trim());
|
|
30
|
+
});
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
stdin.resume();
|
|
34
|
+
stdin.setEncoding("utf8");
|
|
35
|
+
let secret = "";
|
|
36
|
+
const onData = (ch) => {
|
|
37
|
+
if (ch === "\n" || ch === "\r" || ch === "\u0004") {
|
|
38
|
+
cleanup();
|
|
39
|
+
stdout.write("\n");
|
|
40
|
+
resolve(secret);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (ch === "\u0003") {
|
|
44
|
+
cleanup();
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
47
|
+
if (ch === "\u007f" || ch === "\b") {
|
|
48
|
+
if (secret.length) {
|
|
49
|
+
secret = secret.slice(0, -1);
|
|
50
|
+
stdout.write("\b \b");
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
secret += ch;
|
|
55
|
+
stdout.write("*");
|
|
56
|
+
};
|
|
57
|
+
const cleanup = () => {
|
|
58
|
+
stdin.setRawMode(false);
|
|
59
|
+
stdin.pause();
|
|
60
|
+
stdin.removeListener("data", onData);
|
|
61
|
+
};
|
|
62
|
+
stdin.on("data", onData);
|
|
63
|
+
stdin.on("error", (err) => {
|
|
64
|
+
cleanup();
|
|
65
|
+
reject(err);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=read-secret.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-secret.js","sourceRoot":"","sources":["../../src/lib/read-secret.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,MAAc;IACjD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;IAC5B,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAE9B,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACjB,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QACtE,IAAI,CAAC;YACH,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,uCAAuC,CAAC,CAAC;YAC/D,OAAO,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACjD,CAAC;gBAAS,CAAC;YACT,EAAE,CAAC,KAAK,EAAE,CAAC;QACb,CAAC;IACH,CAAC;IAED,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACrB,IAAI,CAAC;YACH,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACzB,CAAC;QAAC,MAAM,CAAC;YACP,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YACxB,MAAM,EAAE,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;YAC7D,EAAE,CAAC,QAAQ,CAAC,qBAAqB,EAAE,CAAC,MAAc,EAAE,EAAE;gBACpD,EAAE,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;YACzB,CAAC,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,KAAK,CAAC,MAAM,EAAE,CAAC;QACf,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QAC1B,IAAI,MAAM,GAAG,EAAE,CAAC;QAEhB,MAAM,MAAM,GAAG,CAAC,EAAU,EAAE,EAAE;YAC5B,IAAI,EAAE,KAAK,IAAI,IAAI,EAAE,KAAK,IAAI,IAAI,EAAE,KAAK,QAAQ,EAAE,CAAC;gBAClD,OAAO,EAAE,CAAC;gBACV,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBACnB,OAAO,CAAC,MAAM,CAAC,CAAC;gBAChB,OAAO;YACT,CAAC;YACD,IAAI,EAAE,KAAK,QAAQ,EAAE,CAAC;gBACpB,OAAO,EAAE,CAAC;gBACV,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;YACD,IAAI,EAAE,KAAK,QAAQ,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;gBACnC,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;oBAClB,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;oBAC7B,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;gBACxB,CAAC;gBACD,OAAO;YACT,CAAC;YACD,MAAM,IAAI,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC,CAAC;QAEF,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YACxB,KAAK,CAAC,KAAK,EAAE,CAAC;YACd,KAAK,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACvC,CAAC,CAAC;QAEF,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACzB,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACxB,OAAO,EAAE,CAAC;YACV,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type RiskLevel = "low" | "medium" | "high" | "unknown";
|
|
2
|
+
export interface ReviewRequest {
|
|
3
|
+
diff: string;
|
|
4
|
+
language: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ReviewResponse {
|
|
7
|
+
markdown: string;
|
|
8
|
+
risk: RiskLevel;
|
|
9
|
+
}
|
|
10
|
+
export interface Provider {
|
|
11
|
+
review(request: ReviewRequest): Promise<ReviewResponse>;
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Provider, ReviewRequest, ReviewResponse } from "./index.js";
|
|
2
|
+
export interface OpenAIProviderOptions {
|
|
3
|
+
apiKey: string;
|
|
4
|
+
baseURL?: string;
|
|
5
|
+
model: string;
|
|
6
|
+
}
|
|
7
|
+
interface ProviderTemplate {
|
|
8
|
+
baseURL: string;
|
|
9
|
+
defaultModel: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function listProviderIds(): string[];
|
|
12
|
+
export declare function resolveProviderDefaults(providerName?: string): ProviderTemplate;
|
|
13
|
+
export declare class OpenAIProvider implements Provider {
|
|
14
|
+
private readonly client;
|
|
15
|
+
private readonly model;
|
|
16
|
+
constructor(options: OpenAIProviderOptions);
|
|
17
|
+
review(request: ReviewRequest): Promise<ReviewResponse>;
|
|
18
|
+
private buildPrompt;
|
|
19
|
+
}
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
const PROVIDER_TEMPLATES = {
|
|
3
|
+
openai: { baseURL: "https://api.openai.com/v1", defaultModel: "gpt-4o" },
|
|
4
|
+
anthropic: {
|
|
5
|
+
baseURL: "https://api.anthropic.com/v1",
|
|
6
|
+
defaultModel: "claude-sonnet-4-5",
|
|
7
|
+
},
|
|
8
|
+
gemini: {
|
|
9
|
+
baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
10
|
+
defaultModel: "gemini-2.5-flash",
|
|
11
|
+
},
|
|
12
|
+
deepseek: {
|
|
13
|
+
baseURL: "https://api.deepseek.com",
|
|
14
|
+
defaultModel: "deepseek-chat",
|
|
15
|
+
},
|
|
16
|
+
groq: {
|
|
17
|
+
baseURL: "https://api.groq.com/openai/v1",
|
|
18
|
+
defaultModel: "llama-3.3-70b-versatile",
|
|
19
|
+
},
|
|
20
|
+
mistral: {
|
|
21
|
+
baseURL: "https://api.mistral.ai/v1",
|
|
22
|
+
defaultModel: "mistral-large-latest",
|
|
23
|
+
},
|
|
24
|
+
together: {
|
|
25
|
+
baseURL: "https://api.together.xyz/v1",
|
|
26
|
+
defaultModel: "meta-llama/Llama-3.3-70B-Instruct-Turbo",
|
|
27
|
+
},
|
|
28
|
+
fireworks: {
|
|
29
|
+
baseURL: "https://api.fireworks.ai/inference/v1",
|
|
30
|
+
defaultModel: "accounts/fireworks/models/llama-v3p3-70b-instruct",
|
|
31
|
+
},
|
|
32
|
+
openrouter: {
|
|
33
|
+
baseURL: "https://openrouter.ai/api/v1",
|
|
34
|
+
defaultModel: "anthropic/claude-3.5-sonnet",
|
|
35
|
+
},
|
|
36
|
+
cerebras: {
|
|
37
|
+
baseURL: "https://api.cerebras.ai/v1",
|
|
38
|
+
defaultModel: "llama-3.3-70b",
|
|
39
|
+
},
|
|
40
|
+
glm: {
|
|
41
|
+
baseURL: "https://api.z.ai/api/coding/paas/v4",
|
|
42
|
+
defaultModel: "glm-4.7",
|
|
43
|
+
},
|
|
44
|
+
ollama: { baseURL: "http://localhost:11434/v1", defaultModel: "llama3.1" },
|
|
45
|
+
/** OpenAI-compatible: https://agentrouter.org/v1 */
|
|
46
|
+
agentrouter: {
|
|
47
|
+
baseURL: "https://agentrouter.org/v1",
|
|
48
|
+
defaultModel: "gpt-4o",
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
/** Default bila env/config tidak menyebut provider (AgentRouter). */
|
|
52
|
+
const DEFAULT_PROVIDER = "agentrouter";
|
|
53
|
+
export function listProviderIds() {
|
|
54
|
+
return Object.keys(PROVIDER_TEMPLATES).sort();
|
|
55
|
+
}
|
|
56
|
+
export function resolveProviderDefaults(providerName) {
|
|
57
|
+
if (!providerName) {
|
|
58
|
+
return PROVIDER_TEMPLATES[DEFAULT_PROVIDER];
|
|
59
|
+
}
|
|
60
|
+
return (PROVIDER_TEMPLATES[providerName.toLowerCase()] ??
|
|
61
|
+
PROVIDER_TEMPLATES[DEFAULT_PROVIDER]);
|
|
62
|
+
}
|
|
63
|
+
export class OpenAIProvider {
|
|
64
|
+
client;
|
|
65
|
+
model;
|
|
66
|
+
constructor(options) {
|
|
67
|
+
this.client = new OpenAI({
|
|
68
|
+
apiKey: options.apiKey,
|
|
69
|
+
baseURL: options.baseURL,
|
|
70
|
+
});
|
|
71
|
+
this.model = options.model;
|
|
72
|
+
}
|
|
73
|
+
async review(request) {
|
|
74
|
+
const prompt = this.buildPrompt(request);
|
|
75
|
+
const response = await this.client.chat.completions.create({
|
|
76
|
+
model: this.model,
|
|
77
|
+
temperature: 0.2,
|
|
78
|
+
messages: [
|
|
79
|
+
{
|
|
80
|
+
role: "system",
|
|
81
|
+
content: "You are ReviuAh, a senior software reviewer. You MUST output valid Markdown and strictly follow the required headings and order.",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
role: "user",
|
|
85
|
+
content: prompt,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
});
|
|
89
|
+
const markdown = response.choices[0]?.message?.content?.trim() ?? "";
|
|
90
|
+
const risk = extractRiskLevel(markdown);
|
|
91
|
+
return { markdown, risk };
|
|
92
|
+
}
|
|
93
|
+
buildPrompt(request) {
|
|
94
|
+
return [
|
|
95
|
+
"Review the following git diff.",
|
|
96
|
+
`Output language: ${request.language}.`,
|
|
97
|
+
"Return Markdown with EXACTLY these sections and order:",
|
|
98
|
+
"## Summary",
|
|
99
|
+
"## Risk Level",
|
|
100
|
+
"Low | Medium | High | Unknown",
|
|
101
|
+
"Reason:",
|
|
102
|
+
"## Security Review",
|
|
103
|
+
"## Performance Review",
|
|
104
|
+
"## Testing Suggestions",
|
|
105
|
+
"## Code Quality & Maintainability",
|
|
106
|
+
"## Actionable Suggestions",
|
|
107
|
+
"Keep the review concise and practical.",
|
|
108
|
+
"\nGit diff:\n",
|
|
109
|
+
request.diff,
|
|
110
|
+
].join("\n");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function extractRiskLevel(markdown) {
|
|
114
|
+
const match = markdown.match(/##\s*Risk Level[\s\S]*?\b(low|medium|high|unknown)\b/i);
|
|
115
|
+
const risk = match?.[1]?.toLowerCase();
|
|
116
|
+
if (risk === "low" ||
|
|
117
|
+
risk === "medium" ||
|
|
118
|
+
risk === "high" ||
|
|
119
|
+
risk === "unknown") {
|
|
120
|
+
return risk;
|
|
121
|
+
}
|
|
122
|
+
return "unknown";
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=openai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/providers/openai.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "reviuah",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AI-powered Git diff reviewer CLI — staged, branch vs base, CI-friendly",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/rsuregar/reviewah.git"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"cli",
|
|
13
|
+
"git",
|
|
14
|
+
"diff",
|
|
15
|
+
"code-review",
|
|
16
|
+
"ai",
|
|
17
|
+
"llm",
|
|
18
|
+
"openai",
|
|
19
|
+
"pre-commit",
|
|
20
|
+
"ci"
|
|
21
|
+
],
|
|
22
|
+
"type": "module",
|
|
23
|
+
"bin": {
|
|
24
|
+
"reviuah": "dist/cli.js",
|
|
25
|
+
"reviewah": "dist/cli.js"
|
|
26
|
+
},
|
|
27
|
+
"files": ["dist", "README.md", "LICENSE"],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc -p tsconfig.json",
|
|
30
|
+
"check": "tsc --noEmit",
|
|
31
|
+
"start": "node dist/cli.js",
|
|
32
|
+
"prepublishOnly": "npm run build",
|
|
33
|
+
"prepare": "node -e \"const f=require('fs');const{execSync}=require('child_process');if(f.existsSync('tsconfig.json')&&f.existsSync('src'))execSync('npm run build',{stdio:'inherit'})\""
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"commander": "^12.1.0",
|
|
40
|
+
"execa": "^9.3.1",
|
|
41
|
+
"openai": "^4.67.3"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^25.3.3",
|
|
45
|
+
"typescript": "^5.6.3"
|
|
46
|
+
},
|
|
47
|
+
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
|
48
|
+
}
|