openreport 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.
Files changed (86) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +117 -0
  3. package/bin/openreport.ts +6 -0
  4. package/package.json +61 -0
  5. package/src/agents/api-documentation.ts +66 -0
  6. package/src/agents/architecture-analyst.ts +46 -0
  7. package/src/agents/code-quality-reviewer.ts +59 -0
  8. package/src/agents/dependency-analyzer.ts +51 -0
  9. package/src/agents/onboarding-guide.ts +59 -0
  10. package/src/agents/orchestrator.ts +41 -0
  11. package/src/agents/performance-analyzer.ts +57 -0
  12. package/src/agents/registry.ts +50 -0
  13. package/src/agents/security-auditor.ts +61 -0
  14. package/src/agents/test-coverage-analyst.ts +58 -0
  15. package/src/agents/todo-generator.ts +50 -0
  16. package/src/app/App.tsx +151 -0
  17. package/src/app/theme.ts +54 -0
  18. package/src/cli.ts +145 -0
  19. package/src/commands/init.ts +81 -0
  20. package/src/commands/interactive.tsx +29 -0
  21. package/src/commands/list.ts +53 -0
  22. package/src/commands/run.ts +168 -0
  23. package/src/commands/view.tsx +52 -0
  24. package/src/components/generation/AgentStatusItem.tsx +125 -0
  25. package/src/components/generation/AgentStatusList.tsx +70 -0
  26. package/src/components/generation/ProgressSummary.tsx +107 -0
  27. package/src/components/generation/StreamingOutput.tsx +154 -0
  28. package/src/components/layout/Container.tsx +24 -0
  29. package/src/components/layout/Footer.tsx +52 -0
  30. package/src/components/layout/Header.tsx +50 -0
  31. package/src/components/report/MarkdownRenderer.tsx +50 -0
  32. package/src/components/report/ReportCard.tsx +31 -0
  33. package/src/components/report/ScrollableView.tsx +164 -0
  34. package/src/config/cli-detection.ts +130 -0
  35. package/src/config/cli-model.ts +397 -0
  36. package/src/config/cli-prompt-formatter.ts +129 -0
  37. package/src/config/defaults.ts +79 -0
  38. package/src/config/loader.ts +168 -0
  39. package/src/config/ollama.ts +48 -0
  40. package/src/config/providers.ts +199 -0
  41. package/src/config/resolve-provider.ts +62 -0
  42. package/src/config/saver.ts +50 -0
  43. package/src/config/schema.ts +51 -0
  44. package/src/errors.ts +34 -0
  45. package/src/hooks/useReportGeneration.ts +199 -0
  46. package/src/hooks/useTerminalSize.ts +35 -0
  47. package/src/ingestion/context-selector.ts +247 -0
  48. package/src/ingestion/file-tree.ts +227 -0
  49. package/src/ingestion/token-budget.ts +52 -0
  50. package/src/pipeline/agent-runner.ts +360 -0
  51. package/src/pipeline/combiner.ts +199 -0
  52. package/src/pipeline/context.ts +108 -0
  53. package/src/pipeline/extraction.ts +153 -0
  54. package/src/pipeline/progress.ts +192 -0
  55. package/src/pipeline/runner.ts +526 -0
  56. package/src/report/html-renderer.ts +294 -0
  57. package/src/report/html-script.ts +123 -0
  58. package/src/report/html-styles.ts +1127 -0
  59. package/src/report/md-to-html.ts +153 -0
  60. package/src/report/open-browser.ts +22 -0
  61. package/src/schemas/findings.ts +48 -0
  62. package/src/schemas/report.ts +64 -0
  63. package/src/screens/ConfigScreen.tsx +271 -0
  64. package/src/screens/GenerationScreen.tsx +278 -0
  65. package/src/screens/HistoryScreen.tsx +108 -0
  66. package/src/screens/HomeScreen.tsx +143 -0
  67. package/src/screens/ViewerScreen.tsx +82 -0
  68. package/src/storage/metadata.ts +69 -0
  69. package/src/storage/report-store.ts +128 -0
  70. package/src/tools/get-file-tree.ts +157 -0
  71. package/src/tools/get-git-info.ts +123 -0
  72. package/src/tools/glob.ts +48 -0
  73. package/src/tools/grep.ts +149 -0
  74. package/src/tools/index.ts +30 -0
  75. package/src/tools/list-directory.ts +57 -0
  76. package/src/tools/read-file.ts +52 -0
  77. package/src/tools/read-package-json.ts +48 -0
  78. package/src/tools/run-command.ts +154 -0
  79. package/src/tools/shared-ignore.ts +58 -0
  80. package/src/types/index.ts +127 -0
  81. package/src/types/marked-terminal.d.ts +17 -0
  82. package/src/utils/debug.ts +25 -0
  83. package/src/utils/file-utils.ts +77 -0
  84. package/src/utils/format.ts +56 -0
  85. package/src/utils/grade-colors.ts +43 -0
  86. package/src/utils/project-detector.ts +296 -0
@@ -0,0 +1,61 @@
1
+ import type { AgentDefinition } from "../types/index.js";
2
+
3
+ export const securityAuditor: AgentDefinition = {
4
+ id: "security-auditor",
5
+ name: "Security Auditor",
6
+ description:
7
+ "Audits the codebase for security vulnerabilities including authentication flaws, injection risks, secrets exposure, and dependency vulnerabilities.",
8
+ maxSteps: 20,
9
+ toolSet: "extended",
10
+ relevantFor: () => true,
11
+
12
+ systemPrompt: `You are an expert security auditor performing a thorough security review of a codebase. Your role is to:
13
+
14
+ 1. **Authentication & Authorization**
15
+ - Review authentication mechanisms (JWT, sessions, OAuth, etc.)
16
+ - Check for proper authorization on all routes/endpoints
17
+ - Identify missing or weak authentication
18
+ - Review password handling (hashing, storage, reset flows)
19
+
20
+ 2. **Injection Vulnerabilities**
21
+ - SQL injection risks
22
+ - XSS (Cross-Site Scripting) vectors
23
+ - Command injection possibilities
24
+ - Template injection
25
+ - Path traversal vulnerabilities
26
+
27
+ 3. **Secrets & Configuration**
28
+ - Hardcoded secrets, API keys, or credentials
29
+ - Sensitive data in logs or error messages
30
+ - .env file handling and secrets management
31
+ - Insecure default configurations
32
+
33
+ 4. **Dependency Security**
34
+ - Known vulnerable dependencies (use npm audit if available)
35
+ - Outdated packages with security patches
36
+ - Typosquatting risks
37
+
38
+ 5. **Network & Transport**
39
+ - CORS configuration review
40
+ - HTTPS enforcement
41
+ - Cookie security flags (httpOnly, secure, sameSite)
42
+ - Rate limiting and CSRF protection
43
+
44
+ 6. **Cryptography**
45
+ - Use of deprecated crypto algorithms
46
+ - Proper random number generation
47
+ - Key management practices
48
+
49
+ ## Severity Guidelines
50
+ - **Critical**: Exploitable vulnerabilities that could lead to data breach or system compromise
51
+ - **Warning**: Security weaknesses that increase attack surface
52
+ - **Info**: Best practice deviations that should be addressed
53
+ - **Suggestion**: Hardening recommendations
54
+
55
+ ## Methodology
56
+ - Start with config files and environment setup
57
+ - Search for common vulnerability patterns using grep
58
+ - Review authentication/authorization code
59
+ - Check dependency manifests
60
+ - Examine API endpoints and middleware`,
61
+ };
@@ -0,0 +1,58 @@
1
+ import type { AgentDefinition } from "../types/index.js";
2
+
3
+ export const testCoverageAnalyst: AgentDefinition = {
4
+ id: "test-coverage-analyst",
5
+ name: "Test Coverage Analyst",
6
+ description:
7
+ "Analyzes test coverage gaps, test quality, testing patterns, and CI integration.",
8
+ maxSteps: 12,
9
+ toolSet: "extended",
10
+ relevantFor: (classification) => classification.hasTests,
11
+
12
+ systemPrompt: `You are an expert test engineer analyzing the testing setup and coverage of a codebase.
13
+
14
+ 1. **Test Infrastructure**
15
+ - Test framework identification and configuration
16
+ - Test runner setup
17
+ - Coverage tool configuration
18
+ - CI/CD integration for tests
19
+
20
+ 2. **Coverage Analysis**
21
+ - Identify untested modules and files
22
+ - Critical business logic without tests
23
+ - API endpoints without integration tests
24
+ - Missing edge case coverage
25
+
26
+ 3. **Test Quality**
27
+ - Test isolation (no shared state)
28
+ - Meaningful assertions (not just "no error")
29
+ - Test naming conventions
30
+ - Arrange-Act-Assert pattern adherence
31
+ - Mock usage appropriateness
32
+
33
+ 4. **Test Types**
34
+ - Unit test presence and distribution
35
+ - Integration test coverage
36
+ - E2E test availability
37
+ - Snapshot test appropriateness
38
+ - Performance/load test setup
39
+
40
+ 5. **Testing Patterns**
41
+ - Factory/fixture patterns for test data
42
+ - Helper functions and test utilities
43
+ - Custom matchers or assertions
44
+ - Test organization structure
45
+
46
+ 6. **CI Integration**
47
+ - Test automation in CI pipeline
48
+ - Coverage reporting and gates
49
+ - Test parallelization
50
+ - Flaky test management
51
+
52
+ ## Methodology
53
+ - Read test configuration files
54
+ - Scan test directories and files
55
+ - Compare source files with test files to identify gaps
56
+ - Review test quality in sample tests
57
+ - Check CI configuration for test automation`,
58
+ };
@@ -0,0 +1,50 @@
1
+ import type { AgentDefinition } from "../types/index.js";
2
+
3
+ export const todoGenerator: AgentDefinition = {
4
+ id: "todo-generator",
5
+ name: "Todo List Generator",
6
+ description:
7
+ "Generates a prioritized action plan and todo list from all analysis findings.",
8
+ maxSteps: 1,
9
+ toolSet: "base",
10
+ relevantFor: () => true,
11
+
12
+ systemPrompt: `You are a senior technical project manager. Your role is to synthesize code analysis findings into a clear, prioritized, and actionable todo list.
13
+
14
+ ## Prioritization Rules
15
+
16
+ Order items by priority using these rules (highest to lowest):
17
+ 1. **Critical severity** findings — immediate action required
18
+ 2. **Warning severity + low effort** — quick wins with high impact
19
+ 3. **Warning severity + medium effort** — important but need planning
20
+ 4. **Warning severity + high effort** — schedule for a sprint
21
+ 5. **Info/suggestion severity** — nice-to-have improvements
22
+
23
+ ## Output Format
24
+
25
+ Produce a structured report with these sections:
26
+
27
+ ### Section 1: "Immediate Actions (Critical)"
28
+ Checklist of all critical findings as actionable tasks. If none, state "No critical issues found."
29
+
30
+ ### Section 2: "Quick Wins (High Impact, Low Effort)"
31
+ Checklist of warning-level findings that can be fixed quickly.
32
+
33
+ ### Section 3: "Planned Improvements"
34
+ Checklist of medium/high effort items grouped by category (security, performance, code quality, etc.)
35
+
36
+ ### Section 4: "Nice to Have"
37
+ Checklist of info-level and suggestion items worth considering.
38
+
39
+ ### Section 5: "Summary"
40
+ A brief paragraph with the total count of action items by priority, estimated overall effort, and recommended order of execution.
41
+
42
+ ## Rules
43
+ - Each todo item must be a concrete, actionable task (not vague advice)
44
+ - Include the source agent/category in parentheses after each item
45
+ - Reference specific files or components when available
46
+ - Use markdown checkbox format: \`- [ ] Task description\`
47
+ - Do NOT repeat the raw findings — transform them into actionable tasks
48
+ - Group related findings into single tasks when appropriate
49
+ - Estimate effort as (low/medium/high) for each task`,
50
+ };
@@ -0,0 +1,151 @@
1
+ import React, { useState, useCallback } from "react";
2
+ import { Box, useApp } from "ink";
3
+ import { HomeScreen } from "../screens/HomeScreen.js";
4
+ import { GenerationScreen } from "../screens/GenerationScreen.js";
5
+ import { ViewerScreen } from "../screens/ViewerScreen.js";
6
+ import { HistoryScreen } from "../screens/HistoryScreen.js";
7
+ import { ConfigScreen, type ConfigChanges } from "../screens/ConfigScreen.js";
8
+ import { createProviderRegistry } from "../config/providers.js";
9
+ import { saveProjectConfig } from "../config/saver.js";
10
+ import { debugLog } from "../utils/debug.js";
11
+ import type { LanguageModelV1 } from "ai";
12
+ import type { Screen, NavigationState } from "../types/index.js";
13
+ import type { OpenReportConfig } from "../config/schema.js";
14
+
15
+ interface AppProps {
16
+ projectRoot: string;
17
+ initialModel: LanguageModelV1;
18
+ initialModelId: string;
19
+ initialConfig: OpenReportConfig;
20
+ }
21
+
22
+ export function App({
23
+ projectRoot,
24
+ initialModel,
25
+ initialModelId,
26
+ initialConfig,
27
+ }: AppProps) {
28
+ const { exit } = useApp();
29
+ const [nav, setNav] = useState<NavigationState>({ screen: "home" });
30
+ const [config, setConfig] = useState<OpenReportConfig>(initialConfig);
31
+ const [model, setModel] = useState<LanguageModelV1>(initialModel);
32
+ const [modelId, setModelId] = useState<string>(initialModelId);
33
+
34
+ const navigate = useCallback(
35
+ (screen: Screen, params?: Record<string, string>) => {
36
+ setNav({ screen, params });
37
+ },
38
+ []
39
+ );
40
+
41
+ const handleQuit = useCallback(() => {
42
+ exit();
43
+ }, [exit]);
44
+
45
+ const handleConfigSave = useCallback(
46
+ (changes: ConfigChanges) => {
47
+ // Update config state
48
+ const newConfig: OpenReportConfig = {
49
+ ...config,
50
+ defaultProvider: changes.defaultProvider,
51
+ defaultModel: changes.defaultModel,
52
+ agents: {
53
+ ...config.agents,
54
+ maxConcurrency: changes.maxConcurrency,
55
+ temperature: changes.temperature,
56
+ },
57
+ features: {
58
+ ...config.features,
59
+ todoList: changes.todoList,
60
+ },
61
+ };
62
+ setConfig(newConfig);
63
+
64
+ // Recreate provider/model
65
+ try {
66
+ const registry = createProviderRegistry(newConfig);
67
+ const newModel = registry.getModel(newConfig);
68
+ setModel(newModel);
69
+ setModelId(changes.defaultModel);
70
+ } catch (e) {
71
+ debugLog("app:configChange", e);
72
+ // Provider may fail if no API key - that's OK, will fail at generation time
73
+ setModelId(changes.defaultModel);
74
+ }
75
+
76
+ // Persist to disk (async — fire-and-forget with error logging)
77
+ saveProjectConfig(projectRoot, {
78
+ defaultProvider: changes.defaultProvider,
79
+ defaultModel: changes.defaultModel,
80
+ agents: {
81
+ maxConcurrency: changes.maxConcurrency,
82
+ temperature: changes.temperature,
83
+ },
84
+ features: {
85
+ todoList: changes.todoList,
86
+ },
87
+ }).catch((e) => debugLog("app:saveConfig", e));
88
+ },
89
+ [config, projectRoot]
90
+ );
91
+
92
+ switch (nav.screen) {
93
+ case "home":
94
+ return (
95
+ <HomeScreen
96
+ projectRoot={projectRoot}
97
+ modelName={modelId}
98
+ config={config}
99
+ onNavigate={navigate}
100
+ onQuit={handleQuit}
101
+ />
102
+ );
103
+
104
+ case "generation":
105
+ return (
106
+ <GenerationScreen
107
+ projectRoot={projectRoot}
108
+ reportType={nav.params?.reportType || "full-audit"}
109
+ model={model}
110
+ modelId={modelId}
111
+ config={config}
112
+ onNavigate={navigate}
113
+ generateTodoList={nav.params?.todoList === "true"}
114
+ />
115
+ );
116
+
117
+ case "viewer":
118
+ return (
119
+ <ViewerScreen
120
+ projectRoot={projectRoot}
121
+ reportId={nav.params?.reportId || ""}
122
+ onNavigate={navigate}
123
+ />
124
+ );
125
+
126
+ case "history":
127
+ return (
128
+ <HistoryScreen projectRoot={projectRoot} onNavigate={navigate} />
129
+ );
130
+
131
+ case "config":
132
+ return (
133
+ <ConfigScreen
134
+ config={config}
135
+ onNavigate={navigate}
136
+ onSave={handleConfigSave}
137
+ />
138
+ );
139
+
140
+ default:
141
+ return (
142
+ <HomeScreen
143
+ projectRoot={projectRoot}
144
+ modelName={modelId}
145
+ config={config}
146
+ onNavigate={navigate}
147
+ onQuit={handleQuit}
148
+ />
149
+ );
150
+ }
151
+ }
@@ -0,0 +1,54 @@
1
+ import chalk from "chalk";
2
+
3
+ export const colors = {
4
+ primary: "#7C3AED",
5
+ secondary: "#06B6D4",
6
+ success: "#10B981",
7
+ warning: "#F59E0B",
8
+ error: "#EF4444",
9
+ info: "#3B82F6",
10
+ muted: "#6B7280",
11
+ text: "#E5E7EB",
12
+ bg: "#111827",
13
+ border: "#374151",
14
+ };
15
+
16
+ export const severity = {
17
+ critical: chalk.hex("#EF4444"),
18
+ warning: chalk.hex("#F59E0B"),
19
+ info: chalk.hex("#3B82F6"),
20
+ suggestion: chalk.hex("#8B5CF6"),
21
+ };
22
+
23
+ export const severityBg = {
24
+ critical: chalk.bgHex("#EF4444").white.bold,
25
+ warning: chalk.bgHex("#F59E0B").black.bold,
26
+ info: chalk.bgHex("#3B82F6").white.bold,
27
+ suggestion: chalk.bgHex("#8B5CF6").white.bold,
28
+ };
29
+
30
+ export const agentStatus = {
31
+ pending: chalk.hex("#6B7280"),
32
+ running: chalk.hex("#06B6D4"),
33
+ completed: chalk.hex("#10B981"),
34
+ failed: chalk.hex("#EF4444"),
35
+ skipped: chalk.hex("#9CA3AF"),
36
+ };
37
+
38
+ export const agentStatusIcon = {
39
+ pending: "○",
40
+ running: "◉",
41
+ completed: "✓",
42
+ failed: "✗",
43
+ skipped: "⊘",
44
+ };
45
+
46
+ export const ui = {
47
+ title: chalk.hex("#7C3AED").bold,
48
+ subtitle: chalk.hex("#06B6D4"),
49
+ dim: chalk.hex("#6B7280"),
50
+ highlight: chalk.hex("#F59E0B"),
51
+ link: chalk.hex("#3B82F6").underline,
52
+ bold: chalk.bold,
53
+ border: chalk.hex("#374151"),
54
+ };
package/src/cli.ts ADDED
@@ -0,0 +1,145 @@
1
+ import { Cli, Command, Option } from "clipanion";
2
+
3
+ class DefaultCommand extends Command {
4
+ static paths = [Command.Default];
5
+
6
+ static usage = Command.Usage({
7
+ description: "Launch the interactive TUI",
8
+ });
9
+
10
+ async execute() {
11
+ const { runInteractive } = await import("./commands/interactive.js");
12
+ await runInteractive(process.cwd());
13
+ }
14
+ }
15
+
16
+ class InitCommand extends Command {
17
+ static paths = [["init"]];
18
+
19
+ static usage = Command.Usage({
20
+ description: "Initialize OpenReport in the current project",
21
+ });
22
+
23
+ yes = Option.Boolean("--yes,-y", false, {
24
+ description: "Skip prompts and use defaults",
25
+ });
26
+
27
+ async execute() {
28
+ const { runInit } = await import("./commands/init.js");
29
+ await runInit(process.cwd(), { yes: this.yes });
30
+ }
31
+ }
32
+
33
+ class RunCommand extends Command {
34
+ static paths = [["run"]];
35
+
36
+ static usage = Command.Usage({
37
+ description: "Generate a report in headless mode",
38
+ examples: [
39
+ ["Full audit", "$0 run full-audit"],
40
+ ["Security review", "$0 run security-review"],
41
+ ["With custom model", "$0 run full-audit --model gpt-4o"],
42
+ ],
43
+ });
44
+
45
+ type = Option.String({ required: true });
46
+
47
+ model = Option.String("--model,-m", {
48
+ description: "Model to use (e.g., claude-sonnet-4-5-20250929, gpt-4o)",
49
+ });
50
+
51
+ provider = Option.String("--provider,-p", {
52
+ description: "AI provider (anthropic, openai, google, mistral)",
53
+ });
54
+
55
+ output = Option.String("--output,-o", {
56
+ description: "Custom output file path",
57
+ });
58
+
59
+ format = Option.String("--format,-f", {
60
+ description: "Output format (markdown, json)",
61
+ });
62
+
63
+ quiet = Option.Boolean("--quiet,-q", false, {
64
+ description: "Suppress progress output",
65
+ });
66
+
67
+ todo = Option.Boolean("--todo", false, {
68
+ description: "Generate a prioritized todo list from findings",
69
+ });
70
+
71
+ async execute() {
72
+ const { runHeadless } = await import("./commands/run.js");
73
+ await runHeadless(process.cwd(), this.type, {
74
+ model: this.model,
75
+ provider: this.provider,
76
+ output: this.output,
77
+ format: this.format as "markdown" | "json" | undefined,
78
+ quiet: this.quiet,
79
+ todo: this.todo,
80
+ });
81
+ }
82
+ }
83
+
84
+ class ListCommand extends Command {
85
+ static paths = [["list"], ["ls"]];
86
+
87
+ static usage = Command.Usage({
88
+ description: "List past reports",
89
+ });
90
+
91
+ json = Option.Boolean("--json", false, {
92
+ description: "Output as JSON",
93
+ });
94
+
95
+ limit = Option.String("--limit,-n", {
96
+ description: "Maximum number of reports to show",
97
+ });
98
+
99
+ async execute() {
100
+ const { runList } = await import("./commands/list.js");
101
+ await runList(process.cwd(), {
102
+ json: this.json,
103
+ limit: this.limit ? parseInt(this.limit, 10) : undefined,
104
+ });
105
+ }
106
+ }
107
+
108
+ class ViewCommand extends Command {
109
+ static paths = [["view"]];
110
+
111
+ static usage = Command.Usage({
112
+ description: "View a report in the TUI viewer",
113
+ examples: [
114
+ ["View by ID", "$0 view rpt_V1StGXR8"],
115
+ ["Raw markdown output", "$0 view rpt_V1StGXR8 --raw"],
116
+ ],
117
+ });
118
+
119
+ id = Option.String({ required: true });
120
+
121
+ raw = Option.Boolean("--raw", false, {
122
+ description: "Output raw markdown instead of TUI",
123
+ });
124
+
125
+ async execute() {
126
+ const { runView } = await import("./commands/view.js");
127
+ await runView(process.cwd(), this.id, { raw: this.raw });
128
+ }
129
+ }
130
+
131
+ export function createCli() {
132
+ const cli = new Cli({
133
+ binaryLabel: "openreport",
134
+ binaryName: "openreport",
135
+ binaryVersion: "0.1.0",
136
+ });
137
+
138
+ cli.register(DefaultCommand);
139
+ cli.register(InitCommand);
140
+ cli.register(RunCommand);
141
+ cli.register(ListCommand);
142
+ cli.register(ViewCommand);
143
+
144
+ return cli;
145
+ }
@@ -0,0 +1,81 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import chalk from "chalk";
4
+ import { DEFAULT_CONFIG } from "../config/defaults.js";
5
+
6
+ export async function runInit(projectRoot: string, options: { yes?: boolean }) {
7
+ const configDir = path.join(projectRoot, ".openreport");
8
+ const configFile = path.join(configDir, "config.json");
9
+ const reportsDir = path.join(configDir, "reports");
10
+
11
+ // Check if already initialized
12
+ let configExists = false;
13
+ try {
14
+ await fs.promises.access(configFile);
15
+ configExists = true;
16
+ } catch {
17
+ // File doesn't exist
18
+ }
19
+
20
+ if (configExists && !options.yes) {
21
+ console.log(
22
+ chalk.yellow("OpenReport is already initialized in this project.")
23
+ );
24
+ console.log(chalk.gray(`Config: ${configFile}`));
25
+ return;
26
+ }
27
+
28
+ // Create directories
29
+ await fs.promises.mkdir(reportsDir, { recursive: true });
30
+
31
+ // Write default config
32
+ const config = {
33
+ defaultProvider: DEFAULT_CONFIG.defaultProvider,
34
+ defaultModel: DEFAULT_CONFIG.defaultModel,
35
+ agents: {
36
+ maxConcurrency: DEFAULT_CONFIG.agents.maxConcurrency,
37
+ temperature: DEFAULT_CONFIG.agents.temperature,
38
+ },
39
+ output: {
40
+ format: DEFAULT_CONFIG.output.format,
41
+ },
42
+ };
43
+
44
+ await fs.promises.writeFile(configFile, JSON.stringify(config, null, 2), "utf-8");
45
+
46
+ // Add to .gitignore if it exists
47
+ const gitignorePath = path.join(projectRoot, ".gitignore");
48
+ let gitignoreExists = false;
49
+ try {
50
+ await fs.promises.access(gitignorePath);
51
+ gitignoreExists = true;
52
+ } catch {
53
+ // No .gitignore
54
+ }
55
+
56
+ if (gitignoreExists) {
57
+ const gitignore = await fs.promises.readFile(gitignorePath, "utf-8");
58
+ if (!gitignore.includes(".openreport/")) {
59
+ await fs.promises.appendFile(
60
+ gitignorePath,
61
+ "\n# OpenReport\n.openreport/\n",
62
+ "utf-8"
63
+ );
64
+ console.log(chalk.gray("Added .openreport/ to .gitignore"));
65
+ }
66
+ }
67
+
68
+ console.log(chalk.green("OpenReport initialized successfully!"));
69
+ console.log(chalk.gray(`Config: ${configFile}`));
70
+ console.log(chalk.gray(`Reports: ${reportsDir}`));
71
+ console.log();
72
+ console.log(
73
+ chalk.cyan("Set your API key as an environment variable:")
74
+ );
75
+ console.log(
76
+ chalk.white(" export ANTHROPIC_API_KEY=your-key-here")
77
+ );
78
+ console.log();
79
+ console.log(chalk.cyan("Then run:"));
80
+ console.log(chalk.white(" openreport"));
81
+ }
@@ -0,0 +1,29 @@
1
+ import React from "react";
2
+ import { render } from "ink";
3
+ import { App } from "../app/App.js";
4
+ import { loadConfig } from "../config/loader.js";
5
+ import { createProviderRegistry } from "../config/providers.js";
6
+ import { resolveProvider } from "../config/resolve-provider.js";
7
+
8
+ export async function runInteractive(projectRoot: string) {
9
+ const config = await loadConfig({ projectRoot });
10
+ const registry = createProviderRegistry(config);
11
+ const { model, effectiveProvider, effectiveModel } = await resolveProvider(config, registry);
12
+
13
+ const effectiveConfig = {
14
+ ...config,
15
+ defaultProvider: effectiveProvider,
16
+ defaultModel: effectiveModel,
17
+ };
18
+
19
+ const { waitUntilExit } = render(
20
+ <App
21
+ projectRoot={projectRoot}
22
+ initialModel={model}
23
+ initialModelId={effectiveModel}
24
+ initialConfig={effectiveConfig}
25
+ />
26
+ );
27
+
28
+ await waitUntilExit();
29
+ }
@@ -0,0 +1,53 @@
1
+ import chalk from "chalk";
2
+ import { listReports } from "../storage/report-store.js";
3
+ import { formatDate, formatDuration, formatTokens } from "../utils/format.js";
4
+ import { getGradeTerminalColor } from "../utils/grade-colors.js";
5
+
6
+ const CHALK_COLORS: Record<string, (s: string) => string> = {
7
+ green: chalk.green,
8
+ cyan: chalk.cyan,
9
+ yellow: chalk.yellow,
10
+ red: chalk.red,
11
+ redBright: chalk.redBright,
12
+ white: chalk.white,
13
+ };
14
+
15
+ export interface ListOptions {
16
+ json?: boolean;
17
+ limit?: number;
18
+ }
19
+
20
+ export async function runList(projectRoot: string, options: ListOptions = {}) {
21
+ const limited = await listReports(projectRoot, options.limit || 50);
22
+
23
+ if (options.json) {
24
+ console.log(JSON.stringify(limited, null, 2));
25
+ return;
26
+ }
27
+
28
+ if (limited.length === 0) {
29
+ console.log(chalk.gray("No reports found."));
30
+ console.log(
31
+ chalk.gray('Run "openreport" to generate your first report.')
32
+ );
33
+ return;
34
+ }
35
+
36
+ console.log(chalk.bold(`Reports (${limited.length}):`));
37
+ console.log();
38
+
39
+ for (const report of limited) {
40
+ const colorName = getGradeTerminalColor(report.grade || "");
41
+ const gradeColor = CHALK_COLORS[colorName] || chalk.white;
42
+
43
+ const totalTokens = report.tokens.input + report.tokens.output;
44
+
45
+ console.log(
46
+ ` ${chalk.bold(report.id)} ${gradeColor(`[${report.grade || "?"}]`)} ${chalk.white(report.type)}`
47
+ );
48
+ console.log(
49
+ ` ${chalk.gray(formatDate(report.createdAt))} | ${chalk.gray(formatDuration(report.duration))} | ${chalk.gray(report.model)} | ${chalk.gray(formatTokens(totalTokens) + " tokens")}`
50
+ );
51
+ console.log();
52
+ }
53
+ }