@shakecodeslikecray/whiterose 1.0.10 → 1.0.12
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/README.md +25 -8
- package/dist/cli/index.js +54 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +48 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# whiterose
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@shakecodeslikecray/whiterose)
|
|
4
|
+
[](https://www.npmjs.com/package/@shakecodeslikecray/whiterose)
|
|
5
|
+
[](https://github.com/shakecodeslikecray/whiterose)
|
|
4
6
|
[](LICENSE)
|
|
5
7
|
|
|
6
8
|
> "I've been staring at your code for a long time."
|
|
@@ -33,6 +35,7 @@ AI-powered bug hunter that uses your existing LLM subscription. No API keys need
|
|
|
33
35
|
- [Bug Categories](#bug-categories)
|
|
34
36
|
- [Output Formats](#output-formats)
|
|
35
37
|
- [Contributing](#contributing)
|
|
38
|
+
- [Show Your Support](#show-your-support)
|
|
36
39
|
- [License](#license)
|
|
37
40
|
|
|
38
41
|
---
|
|
@@ -69,6 +72,7 @@ You need at least one LLM CLI tool installed:
|
|
|
69
72
|
| Codex | `npm install -g @openai/codex` | ✅ Ready |
|
|
70
73
|
| Gemini | `npm install -g @google/gemini-cli` | ✅ Ready |
|
|
71
74
|
| Aider | `pip install aider-chat` | ✅ Ready |
|
|
75
|
+
| OpenCode | `curl -fsSL https://opencode.ai/install \| bash` | ✅ Ready |
|
|
72
76
|
|
|
73
77
|
---
|
|
74
78
|
|
|
@@ -331,13 +335,13 @@ whiterose follows the **Liskov Substitution Principle** - all providers are inte
|
|
|
331
335
|
│ Promise<PromptResult>; │
|
|
332
336
|
│ } │
|
|
333
337
|
└─────────────────────────────────────────────────────────────────┘
|
|
334
|
-
│ │ │ │
|
|
335
|
-
▼ ▼ ▼ ▼
|
|
336
|
-
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
|
|
337
|
-
│ claude │ │ codex │ │ gemini │ │ aider │
|
|
338
|
-
│ -code │ │ │ │ │ │ │
|
|
339
|
-
└──────────┘ └──────────┘ └──────────┘ └──────────┘
|
|
340
|
-
executors/ executors/ executors/ executors/
|
|
338
|
+
│ │ │ │ │
|
|
339
|
+
▼ ▼ ▼ ▼ ▼
|
|
340
|
+
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
|
|
341
|
+
│ claude │ │ codex │ │ gemini │ │ aider │ │ opencode │
|
|
342
|
+
│ -code │ │ │ │ │ │ │ │ │
|
|
343
|
+
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
|
|
344
|
+
executors/ executors/ executors/ executors/ executors/
|
|
341
345
|
```
|
|
342
346
|
|
|
343
347
|
**Adding a new provider is trivial** - just implement the `PromptExecutor` interface (~30 lines).
|
|
@@ -491,7 +495,8 @@ whiterose/
|
|
|
491
495
|
│ │ │ ├── claude-code.ts # ClaudeCodeExecutor
|
|
492
496
|
│ │ │ ├── codex.ts # CodexExecutor
|
|
493
497
|
│ │ │ ├── gemini.ts # GeminiExecutor
|
|
494
|
-
│ │ │
|
|
498
|
+
│ │ │ ├── aider.ts # AiderExecutor
|
|
499
|
+
│ │ │ └── opencode.ts # OpenCodeExecutor
|
|
495
500
|
│ │ │
|
|
496
501
|
│ │ ├── prompts/
|
|
497
502
|
│ │ │ ├── multipass-prompts.ts # Unit pass prompts
|
|
@@ -589,6 +594,18 @@ npm test # Run tests
|
|
|
589
594
|
|
|
590
595
|
---
|
|
591
596
|
|
|
597
|
+
## Show Your Support
|
|
598
|
+
|
|
599
|
+
If you're using whiterose, we'd love to know!
|
|
600
|
+
|
|
601
|
+
- **Star this repo** - helps others discover the project
|
|
602
|
+
- **Add the `whiterose` topic** to your repo - [browse repos using whiterose](https://github.com/topics/whiterose)
|
|
603
|
+
- **Share your experience** - [open a discussion](https://github.com/shakecodeslikecray/whiterose/discussions)
|
|
604
|
+
|
|
605
|
+
[](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/classifying-your-repository-with-topics)
|
|
606
|
+
|
|
607
|
+
---
|
|
608
|
+
|
|
592
609
|
## License
|
|
593
610
|
|
|
594
611
|
PolyForm Noncommercial 1.0.0
|
package/dist/cli/index.js
CHANGED
|
@@ -595,6 +595,52 @@ var OllamaExecutor = class {
|
|
|
595
595
|
}
|
|
596
596
|
}
|
|
597
597
|
};
|
|
598
|
+
var OPENCODE_TIMEOUT = 3e5;
|
|
599
|
+
var OpenCodeExecutor = class {
|
|
600
|
+
name = "opencode";
|
|
601
|
+
async isAvailable() {
|
|
602
|
+
return isProviderAvailable("opencode");
|
|
603
|
+
}
|
|
604
|
+
async runPrompt(prompt, options) {
|
|
605
|
+
const command = getProviderCommand("opencode");
|
|
606
|
+
try {
|
|
607
|
+
const { stdout, stderr } = await execa(
|
|
608
|
+
command,
|
|
609
|
+
["run", prompt],
|
|
610
|
+
{
|
|
611
|
+
cwd: options.cwd,
|
|
612
|
+
timeout: options.timeout || OPENCODE_TIMEOUT,
|
|
613
|
+
env: {
|
|
614
|
+
...process.env,
|
|
615
|
+
NO_COLOR: "1"
|
|
616
|
+
},
|
|
617
|
+
reject: false,
|
|
618
|
+
stdin: "ignore"
|
|
619
|
+
}
|
|
620
|
+
);
|
|
621
|
+
if (stderr) {
|
|
622
|
+
if (stderr.includes("429") || stderr.includes("rate limit") || stderr.includes("quota exceeded")) {
|
|
623
|
+
throw new Error("OpenCode API rate limit reached. Try again later.");
|
|
624
|
+
}
|
|
625
|
+
if (stderr.includes("401") || stderr.includes("403") || stderr.includes("unauthorized") || stderr.includes("invalid api key")) {
|
|
626
|
+
throw new Error("OpenCode API authentication failed. Check your API key.");
|
|
627
|
+
}
|
|
628
|
+
if ((stderr.includes("Error") || stderr.includes("error")) && !stdout) {
|
|
629
|
+
throw new Error(`OpenCode error: ${stderr.substring(0, 200)}`);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return {
|
|
633
|
+
output: stdout || "",
|
|
634
|
+
error: stderr || void 0
|
|
635
|
+
};
|
|
636
|
+
} catch (error) {
|
|
637
|
+
if (error.message?.includes("ENOENT")) {
|
|
638
|
+
throw new Error("OpenCode CLI not found. Install: curl -fsSL https://opencode.ai/install | bash");
|
|
639
|
+
}
|
|
640
|
+
throw error;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
};
|
|
598
644
|
|
|
599
645
|
// src/providers/executors/index.ts
|
|
600
646
|
var executors = {
|
|
@@ -602,7 +648,8 @@ var executors = {
|
|
|
602
648
|
"codex": () => new CodexExecutor(),
|
|
603
649
|
"gemini": () => new GeminiExecutor(),
|
|
604
650
|
"aider": () => new AiderExecutor(),
|
|
605
|
-
"ollama": () => new OllamaExecutor()
|
|
651
|
+
"ollama": () => new OllamaExecutor(),
|
|
652
|
+
"opencode": () => new OpenCodeExecutor()
|
|
606
653
|
};
|
|
607
654
|
function getExecutor(name) {
|
|
608
655
|
const factory = executors[name];
|
|
@@ -5445,14 +5492,16 @@ function getRepoName(cwd) {
|
|
|
5445
5492
|
}
|
|
5446
5493
|
return basename(cwd);
|
|
5447
5494
|
}
|
|
5448
|
-
|
|
5495
|
+
function countLinesOfCode(cwd, files) {
|
|
5449
5496
|
let total = 0;
|
|
5450
5497
|
for (const file of files.slice(0, 500)) {
|
|
5451
5498
|
try {
|
|
5452
|
-
const
|
|
5499
|
+
const filePath = isAbsolute(file) ? file : join(cwd, file);
|
|
5500
|
+
if (!existsSync(filePath)) continue;
|
|
5501
|
+
const content = readFileSync(filePath, "utf-8");
|
|
5453
5502
|
const lines = content.split("\n").filter((line2) => {
|
|
5454
5503
|
const trimmed = line2.trim();
|
|
5455
|
-
return trimmed.length > 0 && !trimmed.startsWith("//") && !trimmed.startsWith("/*") && !trimmed.startsWith("*");
|
|
5504
|
+
return trimmed.length > 0 && !trimmed.startsWith("//") && !trimmed.startsWith("/*") && !trimmed.startsWith("*") && !trimmed.startsWith("#");
|
|
5456
5505
|
});
|
|
5457
5506
|
total += lines.length;
|
|
5458
5507
|
} catch {
|
|
@@ -5753,7 +5802,7 @@ async function scanCommand(paths, options) {
|
|
|
5753
5802
|
);
|
|
5754
5803
|
}
|
|
5755
5804
|
const allBugs = mergeResult.bugs;
|
|
5756
|
-
const linesOfCode =
|
|
5805
|
+
const linesOfCode = countLinesOfCode(cwd, filesToScan);
|
|
5757
5806
|
const scanDuration = Date.now() - scanStartTime;
|
|
5758
5807
|
const bugItems = allBugs.filter((b) => b.kind === "bug");
|
|
5759
5808
|
const smellItems = allBugs.filter((b) => b.kind === "smell");
|