@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 CHANGED
@@ -1,6 +1,8 @@
1
1
  # whiterose
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@shakecodeslikecray/whiterose.svg)](https://www.npmjs.com/package/@shakecodeslikecray/whiterose)
4
+ [![npm downloads](https://img.shields.io/npm/dw/@shakecodeslikecray/whiterose.svg)](https://www.npmjs.com/package/@shakecodeslikecray/whiterose)
5
+ [![GitHub stars](https://img.shields.io/github/stars/shakecodeslikecray/whiterose?style=social)](https://github.com/shakecodeslikecray/whiterose)
4
6
  [![License: PolyForm Noncommercial](https://img.shields.io/badge/License-PolyForm%20NC%201.0-blue.svg)](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
- │ │ │ └── aider.ts # AiderExecutor
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
+ [![Add whiterose topic](https://img.shields.io/badge/Add%20Topic-whiterose-red?style=for-the-badge)](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
- async function countLinesOfCode(cwd, files) {
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 content = readFileSync(join(cwd, file), "utf-8");
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 = await countLinesOfCode(cwd, filesToScan);
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");