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 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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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
@@ -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"}
@@ -0,0 +1,3 @@
1
+ export declare function getStagedDiff(): Promise<string>;
2
+ export declare function getCommitDiff(ref: string): Promise<string>;
3
+ export declare function getRangeDiff(range: string): Promise<string>;
@@ -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,4 @@
1
+ /**
2
+ * Reads a line without echoing characters (TTY). Falls back to visible input if raw mode fails.
3
+ */
4
+ export declare function readSecretLine(prompt: string): Promise<string>;
@@ -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":"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"}
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.js.map
@@ -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
+ }